Conversation
…ed slots Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude <noreply@anthropic.com>
…y envelope Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude <noreply@anthropic.com>
…ing arm Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude <noreply@anthropic.com>
…ns and reference docs Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 25 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 8 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 137 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 6a2531be3b454244746c208bbc2a0d89f8c10e00 && git checkout 6a2531be3b454244746c208bbc2a0d89f8c10e00
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 551139bb78785441c2fee72acd87f0ce835f683b 115de33525f3670d943c958fac501fb4112bc226 && git checkout -B drift-repro 551139bb78785441c2fee72acd87f0ce835f683b && git merge --no-ff 115de33525f3670d943c958fac501fb4112bc226
node scripts/docs-audit/affected-docs.mjs --json 551139bb78785441c2fee72acd87f0ce835f683b
|
A narrowing PR that grows the published export surface widens on a second axis. `shared/evaluated-slot-union.ts` follows the `union-branch-policy` convention: reachable inside `@objectstack/spec`, absent from both barrels, so `api-surface/` and `export-origins/` do not move for it. Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude <noreply@anthropic.com>
Seat verdict: REWORK — the review PASSed and two of its named corrections ship false textSeat What the review settled, and it settled it well⭐ The central question — whether It also gives #18640 its root cause: the instrument's #17618 three-fact spend requires the added member list to be a textual subset of the removed one, and here the member was renamed. ⛔ The gate's prescribed clear is a matcher repair with a self-test case, ⛔ never a false What must change — inside this PR's own surface
⛔ Not owed — do not re-open
Carried, not foldedThe review named two things that are ⛔ not this PR's: Generated by Claude Code |
…s messages
The changeset and the ADR-0087 migration entry both said the union-member
positions leave their sibling arm untouched. Measured on this branch's base
`00115a8442` and at head: true for `RecordAlertProps.visible` (boolean) and
`ServiceLevelIndicator.successCriteria` (structured object), FALSE for
`TraceSamplingConfig.composite[].condition`, whose record arm gained a
`.refine()` and refuses six shapes the base accepted through that arm alone.
A changeset becomes the CHANGELOG and a migration entry becomes the migration
ledger, so both now state the narrowing, its FROM -> TO and its control.
The refine becomes aborting. That is about the MESSAGE and never the accept
set (measured identical either way): non-aborting, it was the surviving arm
for every expression-shaped refusal here, so a blank `source` collided with it
and the slot published a bare `Invalid input` while the sentence sat nested,
and `{ dialect: 'js', source: 'x' }` was refused with a sentence about
`source` that misnames its fault. Aborting hands each refusal back to its
owner: one `custom` issue at `source` for a blank `source`, one
`invalid_union` carrying the published sentence for an `ast`-only envelope or
a blank bare string.
Pins: the tracing slot's accept set, both blank spellings' published sentence,
and the negative (a non-`source` fault is not blamed on `source`); and the
population pin now covers all three refused spellings at all 36 positions
instead of only the `ast`-only one.
Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho
Co-authored-by: Claude <noreply@anthropic.com>
…nce page `gen:migration-registry` picks up the corrected surface / acceptance text, and `gen:docs` republishes `TraceSamplingConfig.composite[].condition` with the `dialect` rule its schema enforces. Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude <noreply@anthropic.com>
…aluated-slot-narrowing
…ribe `check:doc-authoring` flags an internal issue id in customer-facing spec text (maintainer ruling 2026-08-12). The rule the describe publishes is unchanged; only the trailing reference is gone, and the reference page is regenerated. Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS
Generated by Claude Code |
契约复核记录 — 交付后复核(delta 轮),档位
|
⛔ 落地阻断 —— 契约复核 PASS,但本 PR 在 CI 上真红 16 条,且红是本 PR 自己造成的
两件事互不矛盾,先说清楚契约复核的 PASS 成立且在其职权内:它测的是契约语义(接受集、semver 档、公开面),它在自己的隔离检出里只构建了 入队资格是本席的职权:「入队资格 = 每个 check 为 success 或预期 skip,⛔ 不是 required 子集」。本席读了,不合格。 实测(head
|
| 结论 | 条数 |
|---|---|
| 在 main 上也红 | 1 —— TypeScript Type Check |
| 在 main 上是 success,只在本 PR 红 | 16 |
⭐ 仪器是亮的:对照表两个分支都取到了值 —— TypeScript Type Check 命中「main 上也红」,证明该分支可达;⛔ 不是一个永远只会答「你的锅」的坏仪器。
在 main 上绿、只在本 PR 红的 16 条:
Build Core · Test Core + 6 个分片 · Dogfood Regression Gate + 3 个分片 · Dogfood Verify CLI · Type Check · workspace · Type Check · consumer gates · Type Check · debt ledger
根因 —— 一条,不是十六条
十六条全部塌在同一个包的 DTS 构建上(Failed: @objectstack/platform-objects#build),错误逐字:
src/pages/sys-user.page.ts(88,9): error TS2322: Type '{ dialect: "cel" | "cron" | "template";
source?: string | undefined; ast?: unknown; meta?: {...} | undefined; }'
is not assignable to type 'string | { dialect: ...; source: string; ... } | undefined'.
Types of property 'source' are incompatible.
Type 'string | undefined' is not assignable to type 'string'.
⇒ 这正是本 PR 的收窄本身:EvaluatedExpression* 要求 source: string,而 packages/platform-objects/src/pages/sys-user.page.ts:88 仍然递进一个 source 可选的信封。一个第一方消费者没有跟着改。
@objectstack/platform-objects#build 一挂,Build Core、三条 Type Check、六个 Test Core 分片、四条 Dogfood 全部连带塌掉。修好这一处,十六条应当一起回绿 —— 这是预测,⛔ 不是读数,由补丁轮实测。
这恰恰是收窄该有的样子,⛔ 不是复核失职
一次契约收窄本来就该把不合规的调用点照出来。复核测的是「收窄是否正确且已声明」——它是;CI 测的是「谁在用旧形状」——sys-user.page.ts:88 在用。两个读数都对,合起来才是完整判断。本 PR 声明了破坏性变更却没有修自己仓内的消费者。
处置
- 契约复核 PASS 照记(
5715671761),⛔ 不撤、⛔ 不改。 - 本 PR 进补丁轮,原因是 CI 红,⛔ 不是契约 FAIL。
needs:contract-review继续挂着,⛔ 不在此刻摘除。理由说明白以便审计:规则写「PASS ⇒ 剥标、ready、auto-merge」,那是一个以「PR 可落地」为前提的动作单元;本 PR 不可落地,且补丁轮若动到契约面就欠一次 delta 复核。轮次未完,载体不摘。--pair 18638仍读 exit 4(C5)。复核已认定两条 tell 为假、修矫正器归 [finding]check-widening-tells.mjsreports T1 and T2 on lines added only because a zod options object was appended to an existing union — the clause-② enqueue gate refuses a diff that adds no key and no arm #18640、本 PR ⛔ 不得翻Clause-②: yes。⚠️ 本席补记一条给接 [finding]check-widening-tells.mjsreports T1 and T2 on lines added only because a zod options object was appended to an existing union — the clause-② enqueue gate refuses a diff that adds no key and no arm #18640 的人:该卡目前是未定级的finding(只有finding一个标签),按派发规则未定级的 finding 不可派发,归分诊席。⇒ 在 [finding]check-widening-tells.mjsreports T1 and T2 on lines added only because a zod options object was appended to an existing union — the clause-② enqueue gate refuses a diff that adds no key and no arm #18640 被定级并落地之前,本 PR 的--pair读数不会自己变 0;这是本 PR 之后还会撞上的第二道门,现在记下来,免得补丁轮回绿之后再被它挡一次。
Generated by Claude Code
…aluated-slot-narrowing Conflict resolved by hand in packages/spec/src/system/metrics.zod.ts: both intents stack — main's new DurationSeconds import plus this branch's swap of ExpressionInputSchema for EvaluatedExpressionInputSchema and the union refusal helper. ExpressionInputSchema has no remaining use in the merged file. The two both-sides-edited os-regen artifacts take main's side in this commit; the regeneration follows as its own commit. Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude <noreply@anthropic.com>
Discharges the os-regen deferral the merge commit recorded. Both pages carry main's incoming content plus this branch's narrowing: the evaluated-slot envelope now prints `source: string` instead of `source?: string`, and metrics.mdx keeps main's `window.durationSeconds` rename. Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude <noreply@anthropic.com>
`expression()`, `cel`, `tmpl` and `cron` each assign a `string` to `source`
unconditionally, so the `Expression` return type they carried described none of
them — it was slop that cost nothing until an evaluated slot started requiring
`source`. With the slot narrowed, `visibleWhen: P` stopped type-checking at the
spelling this file's own docblock tells authors to use, and
`@objectstack/platform-objects` failed its DTS build on exactly that:
src/pages/sys-user.page.ts(88,9): error TS2322: Type '{ dialect: "cel" |
"cron" | "template"; source?: string | undefined; ... }' is not assignable to
type 'string | { dialect: ...; source: string; ... } | undefined'.
Types of property 'source' are incompatible.
The producer is fixed, not the call site: the value always had a `source`, only
its declared type did not say so. `EvaluatedExpression` is assignable to
`Expression`, so persistence-contract slots keep accepting these values
unchanged; what the narrower return type adds is that evaluated slots accept
them too.
Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho
Co-authored-by: Claude <noreply@anthropic.com>
Fixes #15811
Clause-②: no
Rework round — the seat's three items, measured on this head
Seat verdict #18638 (comment) (REWORK on a PASSed contract review). Three items, nothing else re-opened.
1. The changeset and the ADR-0087 entry said something this diff makes false
Both claimed the three union-member positions leave their sibling arm untouched. Re-measured here, base
00115a8442vs head, parsing each value AS MOUNTED throughTraceSamplingConfigSchema:RecordAlertProps.visiblez.boolean()true/falseacceptedServiceLevelIndicator.successCriteria{ threshold, operator, percentile? }dialectkeyTraceSamplingConfig.composite[].conditionz.record(z.string(), z.unknown())At the tracing slot, six shapes the base accepted through that arm alone — measured: the base's
ExpressionInputSchemarefuses all six, so the record arm was the only thing admitting them — are refused at head:condition{ dialect: 'cel' }{ dialect: 'js', source: 'x' }{ dialect: 'nope', source: 'x' }{ dialect: 'cel', source: 5 }{ dialect: 'cel', source: 'x', meta: { rationale: 5 } }{ dialect: 'zzz', foo: 1 }Control that HITS: a structured filter carrying no
dialectkey —{},{ service: 'api' },{ attributes: { 'http.route': '/v1/orders' } }— is accepted at base and at head alike. Without it the sixrefuseds would be a schema that refuses everything.⭐ The narrowing is correct and load-bearing (it is what makes the ruled change non-inert at that slot) and is not removed. What changed is the description: the changeset now carries the table and its FROM → TO, and the migration entry's
surfaceandacceptanceCriteriaboth name the wider sweep that slot needs — flag everyconditionobject carrying adialectkey, not only the two spellings. A changeset becomes the CHANGELOG and an ADR-0087 entry becomes the migration ledger; neither may ship a false sentence.2. The published reference page
.refine()has no JSON Schema projection — measured against zod 4.4.3:z.toJSONSchemareturns byte-identical output for the plain record, the refined record and the aborting refined record ({"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}). So regenerating alone could never move that TYPE cell, and hand-editing the page is forbidden and would be reverted. The fix is at source: the slot's.describe()now states the rule, andgen:docsrepublishes it.content/docs/references/system/tracing.mdxnow reads:.refine()in the spec, not only this one. Making the page contradict the artifact beside it would be worse. Reported as an out-of-scope finding rather than repaired here.3. The two unpinned message cells — the fix was in the schema, not only in a test
Measured at the slot, on head as it arrived:
{ dialect: 'cel', source: '' }→ one top-levelinvalid_unionwith the bareInvalid input; the published sentence appeared only inside nested arm issues;{ dialect: 'js', source: 'x' }→ refused with the 「needs a non-blanksource」 sentence, which misnames the fault: that value'ssourceis fine, its dialect is not.Root cause, measured: zod 4.4 reports the ONE arm that did not abort, else
invalid_union. The record arm's.refine()was non-aborting, so it was the surviving arm for every expression-shaped refusal here and answered for all of them — and it answered with the other arm's sentence.⇒ The repair is in the schema, not only in a test: the refine becomes aborting, and its message becomes the arm's own rule (module-local, ⛔ not a new published export). Ablation of the accept set: the refused set is identical with and without
abort— both measured over the ten-value corpus above, so this is a message change and not a second narrowing. After it, the slot answers exactly what the other 35 answer, and exactly what the migration entry's own acceptance criteria promise:condition{ dialect: 'cel', source: '' }/' 'invalid_union@ slot,Invalid inputcustomissue @…condition.source, the published sentence{ dialect: 'cel', ast: … }custom@ slot, published sentenceinvalid_union@ slot, published sentence''/' '(bare)invalid_union@ slot, published sentence{ dialect: 'js', source: 'x' }custom@ slot, publishedsourcesentenceinvalid_union@ slot,Invalid input— no longer blamessourcePins, in
packages/spec/src/system/tracing.test.ts: the accept set (six refusals + the accepting control), both blank spellings' published sentence and its exactcode/path, theast-only and bare-string cells, the negative (a non-sourcefault is not answered with thesourcesentence), and the.describe()the reference page renders. Andevaluated-slot-population.test.ts's published-sentence pin now runs all three refused spellings at all 36 positions instead of only theast-only one — 108 cases, all green. That is what would have caught this slot in the first place.Not re-opened
Clause-②staysno(strict subtype, measured over 28 inputs by the review; the two tells are false positives whose matcher repair is card #18640).minor+ BREAKING banner + ADR-0087 disposition stay.printCelAst, the package-internal helper and the 36-position census stay.packages/spec/api-surface/shared.jsonandexport-origins/shared.jsonare still hash-identical to base (git hash-object:cf260910f1…/0429ff67a6…), andgit diff --stat 00115a8442..HEAD -- packages/spec/api-surface packages/spec/export-originsis empty.Gates, re-derived on this head
node scripts/pm/dispatch-gates.mjs --commandson the merged head, every exit code recorded as it ran, reconciled with--ran: 110 derived, 104 run, 6 NOT MEASURED, 0 unrun (--ranexit 0).pnpm --filter @objectstack/spec build && test && typecheckgreen — 486 files / 14016 tests;@objectstack/formula30 files / 871 tests, typecheck green.check:generated: all 15 artifacts up to date after theorigin/mainmerge and the final rebuild.Non-zero exits, all declared:
check:doc-formula-expressions,check:doc-security-posture,check:docs-transcript-drift,check:dual-build-cjs-loads,check:lean-entry-closure,check:type-check-debt) — each refuses an unbuilt workspace closure and says so. NOT MEASURED, not findings;check:skill-examplesexit 1 — the same class in exit-1 clothing (packages/client-react/distholds no.d.ts);check:react-declaration-parityexit 1 — run as CI runs it,MANIFEST="$PWD/sdui.manifest.json" … --strict:111 spec-only divergences, 1 blocks missing from the registry. Control: the identical command at base00115a8442prints the same two numbers, so it is pre-existing and this PR moves neither.One gate went red on this round's own work and is fixed:
check:doc-authoringrefused an internal issue id in customer-facing spec text — the(#15811)this round put in the tracing.describe(). Removed, page regenerated, gate green; the same gate at base is green, so the id was the only offender.node scripts/pm/check-clause2-carriers.mjs --pair 18638— exit 4, and the dual-carrier row C1 is gone: only C5 remains, with the same two false tells (ui/action.zod.ts:833T2,ui/component.zod.ts:1595T1). ⛔ Reported, not acted on; the matcher repair is #18640's.Rework round authored by the
domain:specexecution seat, sessionsession_01LvwGppdonww4zGLWZo5rho.Decision batch #122 item 2 generalised the evaluated-slot rule:
EvaluatedExpressionInputSchemanow composes into every slot an engine evaluates, whileExpressionSchema/ExpressionInputSchemastay the persistence contract (sourceORast) by item 2 of the same ruling. Anast-only envelope and asourcethat is blank after trimming — through the envelope key or the bare-string shorthand — are refused at the door instead of parsing, registering, and faulting at run time.The population was re-derived, not inherited
The census in the card is six days old and
shared/expression.zod.tsmoved after the ruling, so the 36 figure was treated as a premise. Re-derived by identity on this branch's base00115a8442— a negative lookaround on identifier characters, because the bare substring also fires insideCronExpressionInputSchema,TemplateExpressionInputSchemaandEvaluatedExpressionInputSchema, which is the trap that inflated triage's own reading on this card (32 files, five of them Cron-only):ZzzNoSuchSchemaThat 7-versus-17 gap in one file is the trap itself, in miniature. Identical to the measured census (#15811 (comment)), position for position. Two aliases:
ui/action.zod.tsActionConditionInputSchema(mountsvisible+disabled) andsystem/settings-manifest.zod.tsSettingsVisibilityInputSchema(mounts the specifier and manifestvisible). Three positions reach the schema as a union member rather than head-of-declaration.PredicateInputSchemais a plain alias ofExpressionInputSchemawith zero slot users; it stays wide with the schema it aliases.Two defects found while measuring, both fixed here
1. The narrowing was INERT at
TraceSamplingConfig.composite[].condition. That slot isz.union([z.record(z.string(), z.unknown()), …]), and a bare record arm accepts{ dialect: 'cel', ast }as an ordinary record — so swapping the other arm changed nothing. Measured: after the swap and before this fix the slot still answeredsuccess: trueon theast-only envelope, while its 35 siblings answeredfalse. The structured-filter arm now declines an object carrying adialectkey, which is an expression attempt whatever it got wrong. Shipping the swap alone would have been a declared-but-unenforced narrowing.2. Four positions refused with zod's bare
Invalid input. Where the declaration wraps the evaluated schema in a WIDER union — a boolean beside it onaction.visible/action.disabled/RecordAlertProps.visible, a structured object beside it onServiceLevelIndicator.successCriteria— the outer union reportsinvalid_unionat the slot and the inner union's sentence never surfaces.evaluatedExpressionUnionRefusalgives those unions the published sentence. It is deliberately stricter than the inner map it complements: it answers only for a blank string or an object carryingdialect, so a malformed threshold object is not blamed onsource. It lives inshared/evaluated-slot-union.ts, package-internal and absent from both barrels, on theunion-branch-policyconvention: a narrowing PR that grows the published export surface widens on a second axis, soapi-surface/andexport-origins/do not move for it.Item 3 — the printer path is real, and measured
The ruling asked for the lossless direction 「where the dialect has a printer」 before falling back to a structured TODO. Measured rather than assumed:
@marcbachmann/cel-jsshipsserialize, andcel-engine.tsalready uses it for its own scope rewrites. So@objectstack/formulagainsprintCelAst(ast), the inverse of the existingparseCelToAst, and the migration entry prescribes it by name instead of describing a capability nobody can call.Measured round-trip, six sources, each re-evaluated on the same scope:
Lossless about MEANING, not bytes — the printer re-renders from the parse tree, so quote style normalises. Dark controls, all four throwing rather than inventing a source:
{},null,{ type: 'nope' }and a plain string each raiseUnknown AST operation.printCelAstconverts that intonulland additionally requires the printed text to parse back through the platform's own boundedparseCelToAst, so it can never widen what this platform evaluates.Where the printer answers
null, and for every blanksource, the ADR-0087 D3 entryevaluated-expression-slots-source-requiredis the structured TODO — naming the object, the field and the slot, and splitting the judgment by fail policy, because removing a key is safe on the fail-soft half of the population and a silent disclosure on the fail-closed half.Why this is a D3 entry and not a D2 conversion, now that a printer exists. The conversion layer lives in
packages/spec, which is dependency-free by Prime Directive #2 and carries no engine —packages/formula/src/normalize.tsstates the same boundary from the other side. A conversion that had to call the CEL printer could not live where conversions live, and one that guessed without a printer would be the platform inventing a predicate.minor, and the ruling saidmajorItem 3 ordered a 「
majorchangeset」.scripts/check-changeset-no-major.mjsforbids amajormarker during the launch window, because the fixed group versions in lockstep and onemajorpromotes all ~70 packages to a whole-stack major — which is a release act reserved to the maintainer. The guard's own header names the two carriers the convention uses instead, and both are present: the BREAKING banner in the changeset body and the ADR-0087 disposition line. The ruling's substance ships; only the marker differs, and it differs because a repo gate forbids the marker. Flagged rather than chosen silently.Item 4 — the mechanical acceptance surface
#17630 is closed and its widening is live on this base: discovery in
packages/qa/dogfood/test/expression-conformance.test.tsmatches a roster name by identity anywhere on a line, attributes it to thefield:it mounts, and resolves file-local aliases. BothExpressionInputSchemaandEvaluatedExpressionInputSchemaare on that roster, so every one of the 36 positions stays discovered across the swap, the ledger'sfile:Schema.fieldcover keys are unchanged, and theSCAN_CONTROLSfloors (head 37 / inline 3 / alias 2) are unaffected — the swap changes the identifier, never the syntactic shape. No ledger row'sfailPolicymoves: the column records what the EVALUATOR does with a bad expression, and no evaluator changed.Clause-② carrier readings, reported rather than acted on
node scripts/pm/check-clause2-carriers.mjs --pair 18638— exit 4, two rows at the time of writing (re-read on the rework head: C1 has cleared, C5 stands — see the rework section above). ⛔ Neither carrier is touched from here; this is the reading, not a verdict.C1 — the dual carrier is split.
needs:contract-reviewis on card spec: the evaluated-slot rule of #15430 reaches only the flow-node ledger — every otherExpressionInputSchemaslot an engine evaluates (formulaexpression, validation / hook / sharingcondition,visibleWhen…) still accepts anast-only or blank-sourceenvelope #15811 and NOT on this PR. That is the state as found; the seat that owns the gate hangs or clears both sides in one stroke.C5 — three widening tells against
Clause-②: no. One was real and is gone: the new published exportevaluatedExpressionUnionRefusalinapi-surface/shared.json, removed by moving the helper package-internal (above), so the published surface is byte-unchanged by this PR. The remaining two are false, and both for the same reason — the matcher fires on an ADDED LINE that has the shape of a widening, and these two lines were added because an options object was appended to a union that gained no member:ui/action.zod.tsActionConditionInputSchema— read as T2 「a new member of a closed set」. The union has the same two members before and after; what is new on the line is, { error: … }.ui/component.zod.tsRecordAlertProps.visible— read as T1 「a new key on a Zod object schema」.visibleexisted before this PR; the line moved for the same options object.Per the gate's own instruction a false tell is repaired in the matcher (
scripts/pm/check-widening-tells.mjs, with a--self-testcase pinning the shape) or filed as its own card. Repairing ascripts/pm/**matcher is outside this card's surface, so it is filed rather than done here — see the report'sout_of_scope_findings.Tests
packages/spec/src/shared/evaluated-slot-population.test.tsis the new pin, in two halves because either alone is a green that proves nothing:packages/spec/srcstill mounts the persistence schema on a code line, with a lit control (the scan does find the name in the definition file and the barrel), a dark control, and an explicit assertion that the Cron / Template / Evaluated siblings do not leak in as substrings;ExpressionSchema/ExpressionInputSchema/PredicateInputSchemastill ACCEPT both shapes, and a healthy predicate still parses at all 36 (the settings pair gets the predicate its own closed grammar accepts).packages/formula/src/print-cel-ast.test.tspins the printer's two claims, including seven dark-control inputs.Three existing pins were rewritten rather than relaxed — each pinned exactly the arm this PR deletes:
system/settings-manifest.test.ts「anast-only envelope is opaque at this layer」 now pins the refusal, and asserts the settings GRAMMAR message is not the one raised, so the two refusals stay independent;ui/action.test.ts「rejects composition with an AST-only visible loudly (ADR-0078)」 — ADR-0078's promise is unchanged, but the refusal moved from therequiresFeaturelowering to the slot, so it now holds with and without the flag. A second case pins that spec/kernel:requiresFeaturecomposed with a blank-sourcevisiblebuilds a predicate that can never parse — ADR-0078's no-silently-inert guard produces the shape it exists to reject #17631's shape (a blanksourcecomposed into( ) && features.admin == true) can no longer reach the lowering at all;ui/view-form-features-root.test.ts「documented boundary」 now asserts the refusal comes from the evaluated-slot rule and not from the features-root scanner this file is about.Repo census for the migration: zero authored occurrences of either refused spelling outside
packages/spec's own refusal fixtures, acrosspackages/,examples/,content/andskills/, against a lit control that hits. Nothing in this repository needs rewriting.Acceptance notes
PredicateInputSchema(shared/expression.zod.ts) remains a plain value alias ofExpressionInputSchemawith zero slot users. Left wide deliberately — it aliases the persistence contract. Noted, not filed; carrier is the ledger's own limit 2, already written up there.celEngine.evaluateon{ dialect: 'cel', source: '' }answers with the AST-only message rather than an empty-source one. Message accuracy only; the verdict is correct. Unchanged here, still uncarried.Authored by the
domain:specexecution seat, sessionsession_01LvwGppdonww4zGLWZo5rho, under the dispatch claim #15811 (comment).Generated by Claude Code