You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
test(qa): the ADR-0058 D7 expression ledger discovers the cron and template dialects (#15526)
* test(qa): the expression ledger discovers the cron and template dialects
ADR-0058 D7's conformance ratchet re-discovers expression surfaces by SCHEMA
NAME, and `EXPRESSION_INPUT_SCHEMAS` listed only `ExpressionInputSchema` and
`SettingsVisibilityInputSchema`. `DECLARES_EXPRESSION` requires a listed name to
start immediately after the colon, so the 12 positions typed
`CronExpressionInputSchema` / `TemplateExpressionInputSchema` could never match:
the ledger reported a complete classification while carrying zero `cron` and
zero `template` rows. Structurally blind, not merely un-updated.
Widen the roster and classify what it finds, on one commit — adding the names
alone turns the ratchet red with no rows to point at.
Discovery: 26 surfaces -> 36 (+10). The delta is 10 and not 12 because a ratchet
key is `file:field`: `api/export.zod.ts:cronExpression` (`:576`, `:706`) and
`system/disaster-recovery.zod.ts:schedule` (`:57`, `:238`) each collapse two
positions onto one key. Recorded in the `discoverSurfaces` docblock, with the 8
pre-existing collapses that are not all the same surface (filed as #15500).
Five new rows, each naming the reader actually found by walking out from the
declaration — and saying so when none was found:
- `cron-job-schedule` (enforced/throw) — the one cron slot with an evaluator:
`toBoundaryJobSchedule` lowers the envelope and refuses by name, croner runs
it, AppPlugin contains the throw as an ERROR log plus a counter.
- `cron-knowledge-refresh` (experimental) — surfaced, deliberately unscheduled.
- `cron-declared-unwired` (experimental) — five slots on subsystems that were
declared and never built; no evaluator found for any of them.
- `template-prompt` (experimental) — `PromptTemplateSchema` has no consumer.
- `template-title-format` (experimental) — the KEY has a build-time reader that
never evaluates the template; the interpolation site is in objectui and was
not measured from this checkout, so it is not written in as if it had been.
`@objectstack/formula` cronEngine has zero consumers outside its own package, so
no cron slot is syntax-checked anywhere; the rows say that rather than implying
it. Per #15028 the envelope arm accepts any dialect, so the parse does not pin
these to `cron`/`template` either.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
* test(qa): move the ledger rows' tracker ids out of their runtime strings
`check:doc-authoring` flagged 6 (file,id) pairs the new rows introduced: the
`enforcement` and `note` fields are runtime strings that reach authors,
operators and generated surfaces, and none of those readers can resolve a bare
tracker id. The ids move to adjacent `//` comments, where the reader who CAN
resolve them is already looking, and the strings say the same thing
self-containedly.
No baseline entry added — that file is maintainer-only and shrink-only, and
adding one to get green is the path its own failure text refuses. The pinned
population is unchanged at 831 sites.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
---------
Co-authored-by: Claude <noreply@anthropic.com>
// The lowering seam and its containment are #4567; croner, not cron-parser,
281
+
// is the library behind the adapter.
282
+
enforcement:
283
+
'runtime/job-schedule.ts `toBoundaryJobSchedule` — the authoring→boundary seam: it lowers the parsed `{dialect:"cron",source}` envelope to the bare cron string the adapter takes, and THROWS naming the job on a non-cron dialect, an AST-only envelope, or a missing/blank source. Called from runtime/app-plugin.ts `start`; the boundary value reaches service-job/cron-job-adapter.ts `CronJobAdapter.schedule` → **croner** `Cron` (db-job-adapter.ts routes the cron variant there and persists the shape onto sys_job). The throw is CONTAINED at the call site, deliberately and visibly: AppPlugin catches per job, logs `Background job FAILED TO SCHEDULE — it will never run` at ERROR with the `jobScheduleFailuresTotal` counter, then reports the failed count — boot continues and the job does not run. Cron SYNTAX is not judged on this path at all: `toBoundaryJobSchedule` only checks dialect/source shape, and a syntactically invalid pattern throws later inside croner, into the same catch',
note: 'The ONE cron slot in the spec with a measured evaluator. `@objectstack/formula` cronEngine is NOT on this path — see `cron-declared-unwired` for what that means for the rest.',
287
+
},
288
+
{
289
+
// The key and its documented hand-off arrived with #14825.
'PARSE ONLY — `CronExpressionInputSchema` refuses a blank/non-string, non-envelope value and normalizes to `{dialect:"cron",source}`; nothing evaluates the result. service-knowledge/knowledge-service.ts reads `refresh.onRecordChange` and NEVER `refresh.cron` (measured: the only `refresh` reads in that package are the two `onRecordChange` sites)',
295
+
covers: ['ai/knowledge-source.zod.ts:cron'],
296
+
note: 'EXPERIMENTAL by DESIGN, and separated from `cron-declared-unwired` for that reason: the key documents its own hand-off — service-knowledge surfaces the value so an automation flow / external scheduler can call `reindexSource`, and the field docblock says so. Nothing in this repo schedules it, which is the intended state rather than an undelivered one. It still has no evaluator, so it is not `enforced`.',
297
+
},
298
+
{
299
+
// Sibling cards named in this row's note: #15500 (ratchet-key granularity)
300
+
// and #15028 (the envelope arm accepts any dialect).
301
+
id: 'cron-declared-unwired',
302
+
summary: 'cron slots on subsystems that were declared but never built — export schedules, flow schedule state, connector sync, cache warmup, DR backup/test',
'PARSE ONLY — `CronExpressionInputSchema` refuses a blank/non-string, non-envelope value and normalizes to the envelope; NO EVALUATOR FOUND for any of these five keys. Reader hunt, per key, walking out from each declaration (2026-09-04, `61821e54cf5`): `api/export.zod.ts:cronExpression` — the whole `ExportJobApiContracts` family has zero consumers and rest-server serves no `/api/v1/data/export` route, so `POST /api/v1/data/export/schedules` is a declared contract nothing implements; `IExportService` has no provider binding, which its own source already records. `automation/execution.zod.ts:cronExpression` — `ScheduleStateSchema` has no consumer outside packages/spec; the schedule TRIGGER that does work reads a flow start node `config.schedule` through trigger-schedule/schedule-trigger.ts `normalizeSchedule`, a different shape this key never reaches. `integration/connector.zod.ts:schedule` — `syncConfig` has no reader outside packages/spec. `system/cache.zod.ts:schedule` (CacheWarmup) and `system/disaster-recovery.zod.ts:schedule` (BackupConfig + the DR `testing` block) — neither schema has any consumer outside packages/spec',
306
+
covers: [
307
+
'api/export.zod.ts:cronExpression',
308
+
'automation/execution.zod.ts:cronExpression',
309
+
'integration/connector.zod.ts:schedule',
310
+
'system/cache.zod.ts:schedule',
311
+
'system/disaster-recovery.zod.ts:schedule',
312
+
],
313
+
note: 'EXPERIMENTAL — five declared cron slots with no runtime evaluator (ADR-0049 enforce-or-remove candidates; each wants its own look, and the card that surfaced them says so rather than proposing a sweep). ⚠️ TWO of these keys each cover TWO declaring positions, because a ratchet key is `file:field`: `api/export.zod.ts:cronExpression` is `:576` (ScheduledExport) and `:706` (ScheduleExportRequest), and `system/disaster-recovery.zod.ts:schedule` is `:57` (BackupConfig) and `:238` (the DR `testing` block). Both pairs are genuinely the same surface twice, so one row is honest here — but see the sibling card on ratchet-key GRANULARITY, where the same collapse hides surfaces that are NOT the same. ⚠️ The `failPolicy` on this row is `compile-error` because the PARSE is the only thing that ever refuses one of these values; it is not a claim that cron SYNTAX is checked. It is not: `@objectstack/formula` cronEngine validates 5/6-field patterns and `@` aliases, and has ZERO consumers outside packages/formula — nothing routes these slots through it. And per the sibling finding on the dialect union, the envelope arm of `CronExpressionInputSchema` accepts any declared dialect, so even the parse does not pin these to `cron`.',
'PARSE ONLY — `TemplateExpressionInputSchema` refuses a blank/non-string, non-envelope value and normalizes to `{dialect:"template",source}`; NO EVALUATOR FOUND. `PromptTemplateSchema` has no consumer outside packages/spec (measured 2026-09-04), so nothing interpolates the `{{var}}` holes and nothing checks that the declared `variables` match them',
324
+
covers: [
325
+
'ai/model-registry.zod.ts:system',
326
+
'ai/model-registry.zod.ts:user',
327
+
],
328
+
note: 'EXPERIMENTAL — declared prompt templates with no runtime evaluator (ADR-0049). Ownership was checked before classifying rather than assumed: the card that appeared to own these keys is closed as completed, and its delivered diff (`d355c361157`) touched exactly one file, `skills/objectstack-ai/SKILL.md` — it corrected a prose clause that called these keys CEL, and never owned a ledger row. No open card owns them.',
329
+
},
330
+
{
331
+
id: 'template-title-format',
332
+
summary: 'object record-title template (Object.titleFormat, deprecated → nameField per ADR-0079)',
'PARSE ONLY in this repo — `TemplateExpressionInputSchema` refuses a blank/non-string, non-envelope value. The KEY has a build-time reader that is NOT an evaluator: lint/validate-record-title.ts `validateRecordTitle` (wired into authoring-rules.ts, run by `os build` / `os lint` / the MCP authoring surface) reports every declaration as `title-format-retired`, an advisory WARNING steering the author to `nameField` — it reads that the key is present and never looks at the template text. The server-side title resolver deliberately does NOT read it: spec/src/data/display-name.ts `objectTitleCompleteness` / `resolveRecordDisplayName` resolve `nameField` then the `displayNameField` alias then a derivation, and ADR-0079 states the reason (render-only; the server can neither return nor query it)',
336
+
covers: ['data/object.zod.ts:titleFormat'],
337
+
note: 'EXPERIMENTAL, and the state is a deliberate split between two questions. `packages/spec/liveness/object.json` classifies the KEY `live` with the note "objectui ({{record.field}} interpolation)" — that ledger asks whether anything READS the key, and the answer is yes. THIS ledger asks what EVALUATES the expression and under which fail policy, and the only interpolation site named is in the sibling repo objectui, which is not in this checkout: ⛔ NOT measured here, so it is not written into `enforcement` as if it had been. Marking the row `enforced` on a second-hand reading is exactly the invented cell this ledger exists to prevent; marking it `removed` would contradict a governed ledger that measured more than I could. Re-state as `enforced` when someone measures the objectui site — or as `removed` when ADR-0079 retires the key.',
0 commit comments