Skip to content

test(qa): the ADR-0058 D7 expression ledger discovers the cron and template dialects - #15526

Merged
os-litant merged 3 commits into
mainfrom
claude/issue-15027-expression-ledger-cron-template
Sep 4, 2026
Merged

os-litant merged 3 commits into
mainfrom
claude/issue-15027-expression-ledger-cron-template

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes #15027

ADR-0058 D7's conformance ratchet re-discovers expression surfaces by SCHEMA NAME. EXPRESSION_INPUT_SCHEMAS listed two names, and DECLARES_EXPRESSION requires a listed name to start immediately after the colon — so every slot typed CronExpressionInputSchema or TemplateExpressionInputSchema could never match. The ledger reported a complete classification while carrying 14 rows, all dialect: 'cel'. Structurally blind, not merely un-updated.

The roster widening and the rows that classify what it finds land on one commit, deliberately: the names alone turn the ratchet red with nothing to point at.

The delta is +10, not +12 — and that is a finding, not a reconciliation

The card counted 8 cron positions, the dispatch counted 12 (cron 9 + template 3). Both position counts are correct. The ratchet moved by 10:

                        positions   distinct keys   +2 RLS   discoverSurfaces()
roster as-is (2 names)     32            24            2            26
roster widened (4)         44            34            2            36

A ratchet key is file:field, not file:line, so two declarations of one key name in one file are one key. Two of the new pairs collapse: api/export.zod.ts:cronExpression (:576, :706) and system/disaster-recovery.zod.ts:schedule (:57, :238). Both are genuinely the same surface twice, so one row is honest for each.

⭐ Eight keys already collapsed that way before this change, and at least three of them join surfaces that are not the same — data/field.zod.ts:requiredWhen covers both the server-enforced FieldSchema gate and the InlineGridColumnSchema cell whose own describe says "nothing on the write path reads it". That is a separate defect in the instrument, filed as #15500 and deliberately not touched here: changing the key shape re-points every covers entry in the ledger. The arithmetic and the hazard are recorded in the discoverSurfaces docblock so the next author does not read a green ratchet as "every declaration is classified".

The five new rows, and what was actually measured

Every enforcement names what a reader hunt found by walking out from the declaration. Where the hunt found nothing, the row says so instead of borrowing a neighbour's.

row state covers reader
cron-job-schedule enforced / throw system/job.zod.ts:expression toBoundaryJobSchedule → CronJobAdapter → croner
cron-knowledge-refresh experimental ai/knowledge-source.zod.ts:cron surfaced, deliberately unscheduled
cron-declared-unwired experimental export ×1, execution, connector, cache, DR none found
template-prompt experimental ai/model-registry.zod.ts:system, :user none found
template-title-format experimental data/object.zod.ts:titleFormat key read, template not evaluated here

Three measurements worth pulling out, because each one changed a row:

  • Exactly one cron slot in the spec has an evaluator. toBoundaryJobSchedule lowers the envelope and refuses by name; AppPlugin contains the throw as an ERROR log plus a jobScheduleFailuresTotal counter and the job does not run. Cron syntax is never judged on that path.
  • @objectstack/formula cronEngine has zero consumers outside its own package. So no cron slot anywhere is syntax-checked. The rows say that rather than implying a validator exists.
  • template-title-format splits two questions on purpose. packages/spec/liveness/object.json classifies the key live ("objectui {{record.field}} interpolation") — that ledger asks whether anything READS the key. This one asks what EVALUATES the expression. The only interpolation site named is in the sibling repo objectui, which is not in this checkout, so it is not written into enforcement as if it had been measured. The build-time reader that does exist (validateRecordTitle, reporting title-format-retired) reads that the key is present and never looks at the template text; the server-side title resolver deliberately never reads it at all.

Reverse verification — both halves are load-bearing

Two legs, run against the committed tree (the ledger is imported by relative path inside the package, so no dist/ is involved and no rebuild applies):

leg mutation result
A delete the 5 rows, keep the widened roster RED — 10 keys UNCLASSIFIED, 0 STALE
B revert the roster to 2 names, keep the 5 rows RED — 0 UNCLASSIFIED, 10 keys STALE

Leg A proves discovery genuinely finds the 10 keys; leg B proves the roster widening is what makes them visible at all. Each mutation was confirmed on disk before the run (occurrence counts of the removed and injected text), each restore was proved by an empty git diff HEAD, and both files were finally hash-compared against their HEAD blobs.

One bounded in-place correction, named here because it is a correction

cel-flow's comment read "Connector-attached sync (ConnectorSchema.syncConfig) declares no expression surface; nothing to re-point at." Widening the roster falsifies its first clause: syncConfig.schedule is a declared expression surface — a cron one, invisible to discovery when that sentence was written. The comment now says it declares no CEL surface to re-point that cover at, and points at cron-declared-unwired. Same file, same defect class, no new verification surface.

Verification

Gate union derived on the merged tree with dispatch-gates --commands --repo objectstack-ai/objectstack from the paths actually changed, then run at ab90950f0c0 — the final commit: 42/42 green.

check:doc-authoring was genuinely RED first, and the fix is a second commit: the new enforcement/note fields are runtime strings, and I had put six tracker ids inside them. The ids moved to adjacent // comments. No baseline entry was added — that file is maintainer-only and shrink-only, and its pinned population is unchanged at 831 sites.

Also on ab90950f0c0: @objectstack/dogfood typecheck clean, and tsc --listFiles confirms both edited files are in the program, so that green covers this diff rather than skipping it. vitest run test/expression-conformance.test.ts — 3/3 pass. Dependency closure built first.

No changeset: this diff is tests-only inside @objectstack/dogfood, which is "private": true and releases nothing, so the Check Changeset step's route 2 applies and the skip-changeset label is the PREFERRED exemption. Labelled accordingly.

Left alone, on purpose

#15028 is open and remains open: CronExpressionInputSchema pins the dialect only on its bare-string arm, so a cron-typed slot green-parses a cel envelope. That weakens the parse-level enforcement these rows describe, and the rows name it rather than papering over it. Out of scope here.

The five cron-declared-unwired slots and the two prompt keys are ADR-0049 enforce-or-remove candidates. Each wants its own look; classifying them honestly is what this card asked for, and a sweep is not.


🤖 Generated with Claude Code

Generated by Claude Code


Generated by Claude Code

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
`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
@github-actions github-actions Bot added the size/m label Sep 4, 2026
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json ed9d87653eb94b41c769206737110793ffd348be → packageMentionDocs.

@github-actions github-actions Bot added the tests label Sep 4, 2026
@os-litant os-litant added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed tests labels Sep 4, 2026 — with Claude
@os-litant
os-litant marked this pull request as ready for review September 4, 2026 20:10
@os-litant
os-litant enabled auto-merge September 4, 2026 20:11
@os-litant
os-litant added this pull request to the merge queue Sep 4, 2026
Merged via the queue into main with commit e601c04 Sep 4, 2026
43 checks passed
@os-litant
os-litant deleted the claude/issue-15027-expression-ledger-cron-template branch September 4, 2026 20:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants