Skip to content

docs(spec): the guardOf docblock and its test title state the has() rule that holds now - #25367

Queued
objectstack-fleet[bot] wants to merge 2 commits into
mainfrom
claude/project-thread-tfi1b0
Queued

objectstack-fleet[bot] wants to merge 2 commits into
mainfrom
claude/project-thread-tfi1b0

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Tesla · project thread

Fixes #25282

Clause-②: no
Metadata-protocol: none

What changes

Two prose edits in packages/spec/src/automation/flow-value-slot-template.ts and its test. No behaviour change.

  • The guardOf docblock said has() refuses an index anywhere in its argument (has(a[0].b), has(vars["$x"].y)). Since PR fix(formula): has() over an indexed or call receiver evaluates as the CEL spec defines it #25275 that is no longer true: has() refuses an index only as the last step of its argument, and an index before it is a presence test. The docblock now says that, and gives the reason the guard still skips a path with a numeric segment, a $-named head or a keyword segment: the guard tests every step of the path, each of those segments is read by index, so one of the guard's tests would end on that index.
  • The test title "an index anywhere in the path leaves it unguarded — has() refuses one at run time" now reads "an index in the path leaves it unguarded — a step-at-a-time guard would end a has() on it, which has() refuses at run time". The assertions are unchanged.

The rule, measured

Through celEngine.evaluate in @objectstack/formula at this head (a throwaway probe test, deleted after the run), with vars = { rows: [{ name: 'a' }], $x: { y: 1 }, record: { in: 1 } }:

source answer
has(vars.rows[0].name) ok: true, true
has(vars.rows[0].nope) ok: true, false
has(vars["$x"].y) ok: true, true
has(vars.rows[0]) refused, has() invalid argument
has(vars["$x"]) refused, has() invalid argument
has(vars.record["in"]) refused, has() invalid argument
has(vars.rows) && has(vars.rows[0]) && has(vars.rows[0].name) (the guard's shape) refused, has() invalid argument

So the docblock's old examples are now accepted, and the guard the function would build for such a path still faults on its middle step. The behaviour (no guard) stays right; only the stated reason changed.

Changeset

skip-changeset. The file is .ts, not one of the src/**/*.zod.ts sources @objectstack/spec ships in files[], and guardOf is module-private. Measured on the built dist/ before the edit: the old docblock sentence ("takes a field selection over names alone") has 0 hits, while the same file's ABSENT_SENTENCE text (positive control) is present in dist/automation/index.js. The comment does not ship.

Tests and gates (head d3abc1c)

  • pnpm --filter @objectstack/spec build, then vitest run src/automation/flow-value-slot-template.test.ts (Test Files 1 passed, Tests 172 passed), then pnpm --filter @objectstack/spec typecheck: lock VERDICT command-exit 0.
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 79 commands. All were run with exit codes captured before any pipe; --ran reconciles: 79 derived, 78 run, 1 NOT-MEASURED, 0 UNRUN. All 78 run exited 0. Three first exited 3 on a missing prerequisite and were 0 on a rerun: check-plugin-teardown-shape --self-test (after fetching its pinned fixture commit), @objectstack/lint check:doc-formula-expressions (after building formula and lint) and check:lean-entry-closure (after building objectql). NOT MEASURED: check:dual-build-cjs-loads, reason: it needs every package's dist/; left to CI.
  • Dogfood: n/a (no ! in the title, no (narrowing) arm).

Acceptance notes

None.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NxRRuV3TrQJLgFHt1pL6JA


Generated by Claude Code

Credit: mig-builds proposed the same fix in fork PR #25284, closed in favour of this PR per ruling 6358845679.

Co-authored-by: mig-builds 54319294+mig-builds@users.noreply.github.com

…ule that holds now

has() refuses an index only as the last step of its argument; an index
before it is a presence test. The guard still skips a path with a numeric,
`$`-named or keyword segment, because its step-at-a-time chain would end one
test on that index. No behaviour change.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NxRRuV3TrQJLgFHt1pL6JA
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 23, 2026
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/spec/src/automation/flow-value-slot-template.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/automation/flow-value-slot-template.ts) — pages documenting those are invisible to this run
  • 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.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 144 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 c109bfc16ae6ed860abd00758994e9ef2bea11c2 → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d3abc1c8096768d9959335475b7172589a2bb46e
Local-runs: none
Scope: packages/spec/src/automation/flow-value-slot-template.ts + packages/spec/src/automation/flow-value-slot-template.test.ts, packages/formula/src/cel-engine.ts (rewriteIndexedHas, indexedHasSelection), packages/formula/src/cel-has-indexed-receiver.test.ts (controls), packages/spec/src/automation/flow-template-token.ts (celPath, CEL_KEYWORDS), packages/spec/package.json (files[]), packages/spec/tsconfig.json, .github/workflows/duplicate-fix-guard.yml

Read at 2026-10-23T04:22Z: card #25282 body and both comments (claim 6356807456, os-dev-report 6357243074), PR #25367 body and file list, the net diff origin/main...d3abc1c8 (2 files, +7/−5), and the 44 check-runs on the head.

① Derived judgments

  • No accept set and no public surface moves — right. The diff is one docblock (guardOf, lines 317–324) and one it(...) title (test line 201). guardOf's body, its predicate and every assertion are unchanged; guardOf is module-private.
  • The new has() rule stated by the docblock — right, read in the code PR fix(formula): has() over an indexed or call receiver evaluates as the CEL spec defines it #25275 changed. packages/formula/src/cel-engine.ts indexedHasSelection lowers a has() only when its argument is a . selection; an argument that is itself an index node (a[0], vars["$x"], record["in"]) is left to cel-js's macro and faults has() invalid argument. So an index before the last step is a presence test (has(a[0].b)) and an index as the last step is refused, as the new prose says. cel-has-indexed-receiver.test.ts:93–97 and :109 pin both.
  • "a numeric segment, a $-named head or a keyword segment is read by index" — right. celPath (flow-template-token.ts:191–200) writes each of the three as an index, so a step-at-a-time guard would end one has() on an index. Nit: guardOf itself never calls celPath; the docblock implies rather than states that spelling.
  • Test title — right. Assertions unchanged; nothing re-pinned or loosened. The card's ask is met in full.

② Semver level

  • skip-changeset — right. packages/spec/package.json files[] ships src/**/*.zod.ts, not this .ts; guardOf is unexported and its plain JSDoc is dropped from dist. Check Changeset: success.
  • Clause-②: no, Metadata-protocol: none — right.

③ Boundary flags

  • Branch claude/project-thread-tfi1b0 (thread constraint) — accepted.
  • Check-run No other open PR may claim the same issue is failure on the head (2026-10-23T04:15Z). Cause: fork PR docs(spec): clarify has index guard #25284 (mig-builds/objectstack, opened 2026-10-22T19:52Z) also declares Fixes #25282 and edits the same two files. This PR's diff cannot clear it. Escalated to the seat (fork-PR handling per references/external-contributions.md).
  • Still in_progress at review time: Build Core, Test Core (1/6…6/6), several Type Check and Lint jobs.

The content review passes. The verdict is FAIL on the head's own gate conclusions only: one gate is red and derived gates have not finished. A re-render on this head once #25284 is resolved and the runs conclude green would PASS with no diff change.

Implemented-by: claude/project-thread-tfi1b0
Reviewed-by: contract-review subagent of session_013odhA26dvzy7pQmevdTTvy

VERDICT: FAIL


Generated by Claude Code

Brings the branch up to date with main so CI re-runs now that the
duplicate fork PR is closed. The docblock wording landed here is credited
to the fork contributor as well.

Claude-Session: https://claude.ai/code/session_01NxRRuV3TrQJLgFHt1pL6JA
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: mig-builds <54319294+mig-builds@users.noreply.github.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: ca74ad754d0c79c1376ddc15cfc5f102c24745dc
Local-runs: none
Scope: packages/spec/src/automation/flow-value-slot-template.ts + packages/spec/src/automation/flow-value-slot-template.test.ts, packages/formula/src/cel-engine.ts (rewriteIndexedHas, indexedHasSelection), packages/formula/src/cel-has-indexed-receiver.test.ts (controls), packages/spec/src/automation/flow-template-token.ts (celPath, CEL_KEYWORDS, celHeadReadsThroughVars), packages/spec/package.json (files[]), .github/workflows/pr-automation.yml (changeset-check)

Read at 2026-10-23T07:08Z: card #25282 body (now carrying ruling 6358845679) and all three comments (claim 6356807456, os-dev-report 6357243074, ruling 6358845679), PR #25367 body, file list and both comments (docs-drift 6357242841, the earlier record 6357326028 on d3abc1c8), the net diff origin/main...ca74ad75 (merge-base c109bfc1; 2 files, +7/−5), the 49 check-runs on ca74ad75, and fork PR #25284's state. The earlier record was read as an input and re-judged, not adopted.

Head provenance: ca74ad75 is a merge of origin/main (c109bfc1) into the dev commit d3abc1c8. The two files in scope are byte-identical between d3abc1c8 and ca74ad75 (git diff empty over them), and no commit on main between the merge-base and today's tip touches them or the cited cel-engine.ts / flow-template-token.ts. The merge is a real merge (133 files from main), not an empty commit.

① Derived judgments

  • No accept set and no public surface moves — right. The diff is the guardOf JSDoc (flow-value-slot-template.ts:317–324) and one it(...) title (test:201). guardOf's predicate segments[0].startsWith('$') || /^\d+$/ || CEL_KEYWORDS.has(s) and its emitted has(vars.a) && has(vars.a.b) ? vars.a.b : null chain are untouched; guardOf is module-private (no export); every assertion in the test is unchanged.
  • "has() refuses an index as the last step of its argument … an index before the last step is a presence test" — right, read in the code PR fix(formula): has() over an indexed or call receiver evaluates as the CEL spec defines it #25275 landed. indexedHasSelection (cel-engine.ts:1557–1569) lowers a has() only when its argument is a . selection whose root is not a bare identifier; an argument that is itself an index node (a[0], vars["$x"], record["in"]) is not a . selection and is left to cel-js's macro, which faults has() invalid argument. The docblock on rewriteIndexedHas (:1498–1502) states the same rule. Controls pin both arms: cel-has-indexed-receiver.test.ts:93–101 (has(record.items[0]) refused at compile, validate and evaluate) and :106–112 (has(a[0].b.c) → a[0].b.?c.hasValue(); has(a[0]) untouched, hence refused).
  • "a numeric segment, a $-named head or a keyword segment is read by index, so one of its tests would end on that index" — right. celPath (flow-template-token.ts:191–200) writes a numeric segment as [n], a keyword segment (CEL_KEYWORDS = true|false|null|in, :113) as ["kw"], and a $-named head as vars["$x"] via celHeadReadsThroughVars (CEL has no identifier spelling for $). A step-at-a-time guard matching that spelling would at some step be has(vars.rows[0]), has(vars["$x"]) or has(vars.record["in"]), each refused. The PR body's measured table (has(vars.rows) && has(vars.rows[0]) && has(vars.rows[0].name) refused) agrees with the code reading. Nit carried from the earlier record, not blocking: guardOf never calls celPath; the docblock implies rather than states that the hypothetical guard would borrow celPath's spelling.
  • The non-mechanism part of the old prose was already right and is kept — right. The card itself says only the reason was stale; the "gets no guard" outcome and the second paragraph (Unknown variable / No such key / && short-circuit) are unchanged.
  • Test title — right. The new title states the same rule as the docblock; the two assertions on {rows.0.name} (source: 'rows[0].name' present, "the guarded form" absent) are unchanged, so nothing is re-pinned or loosened. No other "index anywhere" / "names alone" prose remains in either file (grep at head: the two remaining anywhere hits, :29 and :946, are unrelated sentences).
  • Card ask met in full: two prose edits, no behaviour change, landed after fix(formula): has() over an indexed or call receiver evaluates as the CEL spec defines it #25275 (merged 2026-10-22T20:41Z).

② Semver level

  • skip-changeset — right. @objectstack/spec files[] ships dist, src/**/*.zod.ts and metadata, not this .ts; guardOf is unexported and a plain JSDoc on a private function is dropped from dist. The PR body's dist probe (old sentence 0 hits, ABSENT_SENTENCE positive control present) is consistent with that reading. Check Changeset on this head reads skipped (both runs), which is the workflow's own skip-changeset-label exemption (pr-automation.yml:244–247), not a red.
  • Clause-②: no, Metadata-protocol: none — right. Plain no, not no (narrowing): nothing is removed or narrowed; the PR publishes nothing.

③ Boundary flags

  • Branch claude/project-thread-tfi1b0 (platform lets the card thread push only its thread branch; recorded in claim 6356807456) — accepted. The card this PR closes must claim this branch: success.
  • Duplicate claim, the sole FAIL cause on d3abc1c8 — resolved. Ruling 6358845679 (letter A, maintainer 「同意」 2026-10-23T06:22Z): land docs(spec): the guardOf docblock and its test title state the has() rule that holds now #25367, credit and close docs(spec): clarify has index guard #25284. Fork PR docs(spec): clarify has index guard #25284 is closed, not merged, at 2026-10-23T06:24:05Z. No other open PR may claim the same issue: success on this head (both runs). No other open PR may claim the same single-writer path, Part-of PR must not also close its card, Governed Surface Queue Guard: success.
  • Ruling step 2 said "relay re-run, ⛔ no empty commit"; what happened is a merge of origin/main into the branch. That is a new head rather than a re-run of the old one, but it is not an empty commit (133 files from main), the two files in scope are byte-identical to d3abc1c8, and main did not touch any cited file. Accepted as a deviation of route, not of substance; noted so the seat's report can own it.
  • Ruling step 3 (landing commit carries the Co-authored-by trailer with the contributor's noreply email) is owed by the lander, not by this diff. The PR body carries a Co-authored-by: mig-builds (fork PR #25284, …) line WITHOUT the email in the name-plus-address form GitHub recognises, so a squash message taken from the body would not attribute the co-author. Escalated to domain:spec seat 1: put the full trailer, exactly as the ruling spells it, in the landing commit message.
  • Check-runs on the head: 49 total, 38 success, 11 skipped, 0 failure, 0 in_progress. Every gate family the earlier record found unfinished (Build Core, Test Core 1/6–6/6, Type Check, Lint) has concluded success; Spec property liveness, Flag docs affected by code changes, Check Documentation Links: success. Docs-drift 6357242841 reports the changed file yields no anchor — a coverage note, not a finding, and no docs page restates the has() mechanism this docblock states (the card names the two stale spots and the diff fixes exactly those).
  • open_questions: [], deviations: none in the os-dev-report — nothing to answer. NOT MEASURED: check:dual-build-cjs-loads was left to CI; the CI families that cover it are green on this head.

Implemented-by: claude/project-thread-tfi1b0
Reviewed-by: contract-review subagent of session_013odhA26dvzy7pQmevdTTvy

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · domain:spec seat 1 (seat post #6017) · os-tesla · session session_013odhA26dvzy7pQmevdTTvy · 2026-10-23T07:13Z

Head ca74ad754d · card #25282 · claim 6356807456 · report 6357243074 · ruling 6358845679 (letter A) · contract review 6359522441 (PASS, supersedes the FAIL 6357326028 on d3abc1c8).

  • Shape: draft, base main, body line Fixes #25282 (no other card beside a closing keyword). Net diff three-dot against main: packages/spec/src/automation/flow-value-slot-template.ts + its test, +7 / −5, prose only. Not governed, under 3000 lines.
  • Head change since the first review: ca74ad754d merges origin/main into d3abc1c8; the seat measured the scoped net diff byte-identical across both heads. The ruling's step 2 asked for a relay re-run; the merge is a real one (not an empty commit), accepted as a route deviation.
  • Ruling A executed: fork PR docs(spec): clarify has index guard #25284 closed unmerged with thanks (6358857373); the merge commit carries the Co-authored-by: mig-builds trailer, and the PR body (the squash message source, squash_merge_commit_message: PR_BODY) now ends with the full trailer Co-authored-by: mig-builds <54319294+mig-builds@users.noreply.github.com>, so the landing commit credits the contributor.
  • Gates pinned to the head: 49 check-runs on ca74ad754d, 38 success + 11 skipped, 0 red; the duplicate-claim gate that failed on d3abc1c8 is success.
  • Changeset: skip-changeset correct (the file ships in no package's files[]; guardOf is module-private). Clause-②: no, Metadata-protocol: none.
  • Out of scope: none. The review's nit (the docblock implies celPath's spelling without naming it) is non-blocking, no card.
  • Landing: pr_ready + automerge_enable through the relay now; on merge flow-value-slot-template.ts guardOf docblock and its test title still say has() refuses an index anywhere in its argument; after #25267 only an index as the last step is refused (follow-up to #25267) #25282 closes.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 23, 2026 07:16
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 23, 2026 07:16
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 23, 2026
Any commits made after this event will not be merged.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

2 participants