Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/now/2026-09-09-inngest-functions-as-t27-specs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# NOW -- The 28 Inngest functions of 999-multibots-telegraf as first-class .t27 specs (2026-09-09)

## Inngest functions as first-class .t27 specs

- `specs/functions/` (28): one `.t27` module per Inngest function the bot `999-multibots-telegraf` registers at `main @a9c08b4` (`functions/<id>.t27`, `KIND = "function"`, module `fn_<id>`). Every constant is `pub const`; the schema is in `specs/functions/README.md`. Layer five of the ladder Specs -> Skills -> Crons -> Agents -> Functions: what one run does step by step (STEPS in source order), what starts it (TRIGGER, EVENT + LEGACY_EVENTS or CRON + TZ), how it fails (RETRIES, ON_FAILURE), what it touches outside (SIDE_EFFECTS), the first step that stops a bad payload (GUARD) and what the 2026-09-09 safe probe reached (SAFE_PROBE, PROBE_RESULT).
- Values were read from the source, not inferred: the functions manifest (28 entries, the code witness the site vendors) plus the checkout itself where the manifest has gaps -- three functions with no file/steps/retries in the manifest and eight with undeclared `retries` (the spec writes the Inngest JS SDK v3 default, 4, and says so in NOTE). One disagreement is kept visible rather than resolved: `payment-ai-server-process` declares an `onFailure` handler that messages the admin chat, the manifest says `log`.
- Not hidden: four functions (`training-model-complete`, `training-stuck-check`, `webhook-generation-validate`, `welcome-avatar-generate`) are not on the production Railway build as of 2026-09-09 (24 base functions deployed, 28 on `main`); their PROBE_RESULT is `not-deployed`, GUARD `unknown`.
- Measured: **28/28** typecheck ok under the vendored wasm (sha256 `4d9c0447b5ca2887...`), nothing discarded, field schema satisfied, all ASCII. The bootstrap compiler on `master` was not run against these files.
- Language: the specs are English-only (LANG-EN). `specs/i18n/agents-ru.t27` `SCOPE` gains `specs/functions` (`[4]str`); the RU bundle (`trinity:apps/website/i18n/agents.ru.json`) gains 28 entries keyed by `ID` in the companion trinity PR.
- Not claimed: no function was renamed in the code by this change; no run was triggered; no silicon, no first/only/best.
- `specs/OWNERS.md` gains the row `functions/` -> **T-Queen**.
1 change: 1 addition & 0 deletions specs/OWNERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
| `isa/` | **C-Compiler** | Register alphabet |
| `skills/`, `crons/`, `i18n/` | **T-Queen** | Agent skills and scheduled jobs as `.t27` specs, plus the per-locale translation contracts (`i18n/agents-<locale>.t27`) that point at the site's text bundles; the site (gHashTag/trinity) generates its catalogs from a vendored copy |
| `agents/` | **T-Queen** | The 27 agents of `docs/agents/AGENTS_ALPHABET.md` as `.t27` specs (`agents/<letter>.t27`, `KIND = "agent"`): letter, domain, archetype, register, invariants, the documents that bind it (`SOUL.md`, `AGENTS.md`, the alphabet) and the skills a source evidently binds; the site joins experience by LETTER |
| `functions/` | **T-Queen** | The 28 Inngest functions of `999-multibots-telegraf` as `.t27` specs (`functions/<id>.t27`, `KIND = "function"`): canonical and legacy ids and events, trigger, steps, retries, failure handling, side effects, guard and the 2026-09-09 safe-probe result; the site joins them to a vendored copy of the functions manifest |

Each subtree with substantial churn should keep a local **`OWNERS.md`** (see below).

Expand Down
112 changes: 112 additions & 0 deletions specs/functions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# specs/functions — Inngest functions of 999-multibots-telegraf as first-class `.t27` specs

> **Where this lives.** This directory in `gHashTag/t27` is the canonical home of these
> specs — edit them here. `gHashTag/trinity` keeps a vendored copy under
> `apps/website/public/t27/files/specs/functions/` and its build reads that copy through the
> vendored compiler wasm (`t27_compiler.wasm`). The wasm's `typecheck.ok` is necessary,
> not sufficient: it stays `true` for a wrong annotation such as `str = 5`, so the site's
> generator (`scripts/agents-from-specs.mjs`) also checks the field schema below. The
> bootstrap compiler on `master` was not run against these files in the commit that
> added them; all 28 were compiled with the vendored wasm (sha256 `4d9c0447b5ca2887...`),
> typecheck ok, nothing discarded.

One file per Inngest function the bot `999-multibots-telegraf` registers (28 at `main
@a9c08b4`). The `.t27` file is the source of truth for the function card on t27.ai
(`#/functions?function=<ID>`); `public/functions/spec-functions.json` is generated from it by
the real compiler. The code witness is a vendored copy of the functions manifest
(`public/functions/manifest.json`, the same 28 entries), extracted from the source tree — a
card with a spec and a manifest entry is labelled `spec+code`, a spec without one `spec-only`,
a manifest entry without a spec `code-only`.

File name: `<ID>.t27` (the canonical id, e.g. `neuro-image-generate.t27`); module name:
`fn_<ID with underscores>` (`fn_neuro_image_generate`).

## The ladder

```
Specs -> Skills -> Crons -> Agents -> Functions
specs/** specs/skills specs/crons specs/agents specs/functions
what starts what one Inngest run does, step by
a run and when step, and what it touches outside
```

A cron-triggered function (`TRIGGER = "cron"`) is also a scheduled job, so five of them are
stated twice: as a cron card in `specs/crons` (`inngest/999-multibots-telegraf/<LEGACY_ID>`)
and as a function card here. The site joins the two by `REPO` + `LEGACY_ID` = the cron's
`NAME`; nothing is inferred from a domain or a file name.

## Schema (every constant is `pub const`)

| constant | type | meaning |
|-----------------|----------|------------------------------------------------------------------------------------------------------------|
| `KIND` | `str` | always `"function"` |
| `ID` | `str` | canonical id (`<domain>-<object>-<verb>`), equals the file name; must equal `id` in the manifest |
| `LEGACY_ID` | `str` | the id the deployed code still registers (`legacy_id` in the manifest) |
| `NAME` | `str` | display name |
| `REPO` | `str` | `999-multibots-telegraf` |
| `SERVICE` | `str` | `file:line` of `inngest.createFunction(` in that repo |
| `DOMAIN` | `str` | `neuro` \| `reels` \| `training` \| `morph` \| `render` \| `payment` \| `broadcast` \| `instagram` \| `content` \| `monitoring` \| `analytics` \| `webhook` \| `welcome` |
| `TRIGGER` | `str` | `event` \| `cron` |
| `EVENT` | `str` | canonical event name; `""` for a cron function |
| `LEGACY_EVENTS` | `[N]str` | event names the code still listens to (multi-trigger); `[0]str = []` for a cron function |
| `CRON` | `str` | five-field cron expression; `""` for an event function |
| `TZ` | `str` | time zone of `CRON` (`UTC`); carried for event functions too |
| `SUMMARY_EN` | `str` | what one run does (English) |
| `STEPS` | `[N]str` | `step.run` names in source order; `${...}` in a name is a template the code expands per item |
| `RETRIES` | `u8` | `retries` declared on the function; when none is declared, `4` (Inngest JS SDK v3 default) and `NOTE` says so |
| `ON_FAILURE` | `str` | `admin-telegram` (an `onFailure` handler messages the admin chat) \| `log` (no handler) \| `refund+notify` |
| `SIDE_EFFECTS` | `[N]str` | from `charges-balance`, `paid-api`, `messages-user`, `messages-owners`, `messages-admin`, `db-write`, `external-webhook`, `none` |
| `GUARD` | `str` | first step that stops a bad payload (`zod-schema`, `check-user`, `validate-input`, `amount-match`, …); `none` when the first step already acts; `unknown` when the function could not be probed |
| `SAFE_PROBE` | `str` | JSON payload sent in the 2026-09-09 safe probe; `""` when none was sent |
| `PROBE_RESULT` | `str` | `COMPLETED` \| `FAILED-at-guard` \| `skipped` (no probe sent) \| `not-deployed` (function absent from the probed build) |
| `CONTROL` | `str` | `spec+code` \| `spec-only` \| `code-only` — what the author expects the site to find |
| `NOTE` | `str` | anything a reader must know that the fields above cannot say |

The generator fails the build when: the compiler verdict is not clean; a constant is
missing, not `pub`, or has the wrong annotation / array length / integer range; `KIND` is not
`"function"`; `ID` does not equal the file name; the module name is not `fn_<ID>`; `TRIGGER`,
`ON_FAILURE`, `PROBE_RESULT` or `CONTROL` is outside its vocabulary; a `SIDE_EFFECTS` value is
outside the list; an event function has `CRON` or no `EVENT`; a cron function has `EVENT`,
`LEGACY_EVENTS` or no `CRON`; or `SAFE_PROBE` is neither `""` nor JSON. A spec whose
`TRIGGER`, `EVENT`, `CRON`, `RETRIES`, `ON_FAILURE`, `STEPS` or `SIDE_EFFECTS` differ from the
manifest entry with the same `ID` is not a build failure: the difference is written on the
card as a message, and the card's health drops to `warn`.

## What was read from where (honesty)

* Source: `999-multibots-telegraf` `main @a9c08b4`. The manifest was extracted from that tree;
where it carries no `file`, no `steps` or `retries: null` (three functions: `training-model-v2-start`,
`payment-ai-server-process`, `broadcast-message-send`, and eight functions with undeclared
`retries`), the spec reads the same checkout directly and says so in `NOTE`.
* `payment-ai-server-process`: the code declares `onFailure: createInngestFailureHandler(...)`,
which logs and messages the admin chat; the manifest says `log`. The spec says
`admin-telegram` and keeps the disagreement in `NOTE`; the site shows it as a message.
* `RETRIES = 4` for undeclared retries is the SDK default, not a declaration. The 2026-09-09
probe saw such functions reach attempt 5, which is consistent with four retries.
* Four functions are **not on the production Railway build** as of 2026-09-09
(`training-model-complete`, `training-stuck-check`, `webhook-generation-validate`,
`welcome-avatar-generate`): the deployed app registers 24 base functions, `main` registers
28. Their `PROBE_RESULT` is `not-deployed`, `GUARD` is `unknown`, and `NOTE` says so. The
site reads the `deployed_2026_09_09` flag from the manifest and shows it on the card.
* Unregistered code in the tree (`test-simple`, `test-simple-message`, `test-advanced-loop`,
`kie-ai-webhook-manual-check`, `voice-training-*`, `webhook-health-check`,
`periodic-webhook-health-check`, the duplicate `morphImages.ts`) has no spec: nothing is
registered silently.
* Live run counts are not in the spec and not in the manifest. The site polls a read-only
status endpoint of the bot; when it is unreachable the card says "status source offline",
and an unknown state is labelled unknown.

## Language

Specs are English-only (t27 LANG-EN; `bootstrap/build.rs` fails the build on Cyrillic) and
ASCII-only. Russian `NAME` / `SUMMARY` travel through the contract
`specs/i18n/agents-ru.t27`, whose `SCOPE` names `specs/functions`; the bundle lives in
`gHashTag/trinity` at `apps/website/i18n/agents.ru.json`, keyed by `ID`.

## Editing

Edit in `gHashTag/t27` (`specs/functions/<ID>.t27`), re-vendor the byte-identical copy into
`gHashTag/trinity` `apps/website/public/t27/files/specs/functions/`, then run
`node scripts/agents-from-specs.mjs` and `npm run check:agents` in `apps/website`. When the
code changes (a rename, a new step, a different `retries`), update the manifest copy and the
spec in the same change; the card shows any distance between the two.
42 changes: 42 additions & 0 deletions specs/functions/analytics-sales-advise.t27
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: Apache-2.0
; specs/functions/analytics-sales-advise.t27 -- function analytics-sales-advise (Inngest, 999-multibots-telegraf)
; Source of truth for the function card on t27.ai (#/functions?function=analytics-sales-advise).
; The registration was read from 999-multibots-telegraf:src/inngest_app/functions/analytics/dailySalesAdvisor.ts#L54 (main @a9c08b4).
; ID is the canonical id of the spec-first rename; LEGACY_ID and LEGACY_EVENTS are what the
; deployed code still answers to. STEPS lists step.run names in source order; a ${...} in a
; name is a template the code expands per item. Values here are read from the source, not
; inferred from a name; what could not be read says so in NOTE.
; phi^2 + 1/phi^2 = 3 | TRINITY

module fn_analytics_sales_advise;

pub const KIND : str = "function";
pub const ID : str = "analytics-sales-advise";
pub const LEGACY_ID : str = "daily-sales-advisor";
pub const NAME : str = "Daily sales advisor";
pub const REPO : str = "999-multibots-telegraf";
; file:line of inngest.createFunction( in the repo named in REPO
pub const SERVICE : str = "src/inngest_app/functions/analytics/dailySalesAdvisor.ts:54";
pub const DOMAIN : str = "analytics";
; "event" or "cron"; an event function has EVENT (+ LEGACY_EVENTS), a cron function has CRON
pub const TRIGGER : str = "cron";
pub const EVENT : str = "";
pub const LEGACY_EVENTS : [0]str = [];
pub const CRON : str = "0 9 * * *";
pub const TZ : str = "UTC";
pub const SUMMARY_EN : str = "Every day at 09:00 UTC loads the bot owners and their payments over the last seven, two and one days and sends each owner a sales report, with a copy to the admin chat.";
; step.run names in source order
pub const STEPS : [5]str = ["load-owners", "load-payments-7d", "load-payments-1d", "load-payments-2d", "report-${ownerId}"];
; retries declared on the function; when the code declares none, the Inngest JS SDK v3 default (4) is written and NOTE says so
pub const RETRIES : u8 = 1;
; "admin-telegram" (onFailure handler messages the admin chat), "log" (no handler) or "refund+notify"
pub const ON_FAILURE : str = "log";
; what a run touches outside the function, from a fixed vocabulary (see README)
pub const SIDE_EFFECTS : [2]str = ["messages-owners", "messages-admin"];
; the first step that stops a bad payload; "none" when the first step already acts, "unknown" when not probed
pub const GUARD : str = "none";
; JSON payload of the 2026-09-09 safe probe and what it reached ("" / "skipped" when none was sent; "not-deployed" when the function is not on the probed build)
pub const SAFE_PROBE : str = "{}";
pub const PROBE_RESULT : str = "COMPLETED";
pub const CONTROL : str = "spec+code";
pub const NOTE : str = "The same schedule is stated as a cron card, specs/crons ID inngest/999-multibots-telegraf/daily-sales-advisor. The step name report-${ownerId} is a template expanded once per owner. A manual invoke on 2026-09-09 messaged 13 owners; the hardening list asks this function to honour safe mode.";
42 changes: 42 additions & 0 deletions specs/functions/analytics-skills-detect.t27
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: Apache-2.0
; specs/functions/analytics-skills-detect.t27 -- function analytics-skills-detect (Inngest, 999-multibots-telegraf)
; Source of truth for the function card on t27.ai (#/functions?function=analytics-skills-detect).
; The registration was read from 999-multibots-telegraf:src/inngest_app/functions/analytics/skillDetector.ts#L30 (main @a9c08b4).
; ID is the canonical id of the spec-first rename; LEGACY_ID and LEGACY_EVENTS are what the
; deployed code still answers to. STEPS lists step.run names in source order; a ${...} in a
; name is a template the code expands per item. Values here are read from the source, not
; inferred from a name; what could not be read says so in NOTE.
; phi^2 + 1/phi^2 = 3 | TRINITY

module fn_analytics_skills_detect;

pub const KIND : str = "function";
pub const ID : str = "analytics-skills-detect";
pub const LEGACY_ID : str = "skill-detector";
pub const NAME : str = "Skill detector";
pub const REPO : str = "999-multibots-telegraf";
; file:line of inngest.createFunction( in the repo named in REPO
pub const SERVICE : str = "src/inngest_app/functions/analytics/skillDetector.ts:30";
pub const DOMAIN : str = "analytics";
; "event" or "cron"; an event function has EVENT (+ LEGACY_EVENTS), a cron function has CRON
pub const TRIGGER : str = "cron";
pub const EVENT : str = "";
pub const LEGACY_EVENTS : [0]str = [];
pub const CRON : str = "0 10 * * *";
pub const TZ : str = "UTC";
pub const SUMMARY_EN : str = "Every day at 10:00 UTC loads the known skills, detects new service types from recent usage, one detection step per service type, and notifies the admin chat.";
; step.run names in source order
pub const STEPS : [3]str = ["load-existing-skills", "detect-${serviceType}", "notify-admin"];
; retries declared on the function; when the code declares none, the Inngest JS SDK v3 default (4) is written and NOTE says so
pub const RETRIES : u8 = 1;
; "admin-telegram" (onFailure handler messages the admin chat), "log" (no handler) or "refund+notify"
pub const ON_FAILURE : str = "log";
; what a run touches outside the function, from a fixed vocabulary (see README)
pub const SIDE_EFFECTS : [2]str = ["messages-owners", "messages-admin"];
; the first step that stops a bad payload; "none" when the first step already acts, "unknown" when not probed
pub const GUARD : str = "none";
; JSON payload of the 2026-09-09 safe probe and what it reached ("" / "skipped" when none was sent; "not-deployed" when the function is not on the probed build)
pub const SAFE_PROBE : str = "{}";
pub const PROBE_RESULT : str = "COMPLETED";
pub const CONTROL : str = "spec+code";
pub const NOTE : str = "The same schedule is stated as a cron card, specs/crons ID inngest/999-multibots-telegraf/skill-detector. The step name detect-${serviceType} is a template expanded once per service type.";
Loading
Loading