Skip to content

docs(spec): mark PromptTemplate.system/.user [EXPERIMENTAL — not enforced] - #16548

Merged
huangyiirene merged 2 commits into
mainfrom
claude/issue-16321-prompt-template-experimental-marking
Sep 7, 2026
Merged

docs(spec): mark PromptTemplate.system/.user [EXPERIMENTAL — not enforced]#16548
huangyiirene merged 2 commits into
mainfrom
claude/issue-16321-prompt-template-experimental-marking

Conversation

@huangyiirene

Copy link
Copy Markdown
Collaborator

Fixes #16321

Clause-②: no — prose only. No accept-set change, no new or narrowed authorable key, no matrix declaration. Every value that parsed before parses now, and every value refused before is refused identically.

The ruling

Under the #15954 ruling (director seat, decision batch #56, option B) the template-typed pair is marked, not retired.

What changed

1. packages/spec/src/ai/model-registry.zod.ts (lines 121-122). Both .describe() strings on PromptTemplateSchema now carry the marking and state that no runtime renders or executes the template today:

system: TemplateExpressionInputSchema.optional().describe('[EXPERIMENTAL — not enforced] System prompt — supports {{var}} interpolation. No runtime renders or executes the template today.'),
user:   TemplateExpressionInputSchema.describe('[EXPERIMENTAL — not enforced] User prompt template — supports {{var}} interpolation. No runtime renders or executes the template today.'),

2. D7 ledger (packages/qa/dogfood/test/expression-conformance.ledger.ts). The template-prompt row records the marking for both positions it covers.

3. Generated reference page regenerated with the repo's own tooling — pnpm --filter @objectstack/spec check:generated --fix, which proved exactly one artifact stale and regenerated only that one. No generated artifact was hand-edited.

4. Changesetpatch on @objectstack/spec, citing the ruling.

The schema shape is untouched

.user remains required (no .optional()) and .system keeps the .optional() it already had. The generated page still renders user as required. Optionalising or retiring a required key is parse-breaking and is deliberately left to its own card.

The card names a model site that does not exist

The card says to copy the form ai/knowledge-source.zod.ts uses for KnowledgeRefreshPolicy.cron. That file carries no such marking. Re-measured independently at 13c08356abc61f13acd5db0932f99d482c134588:

  • git grep -c "EXPERIMENTAL" 13c08356ab -- packages/spec/src/ai/knowledge-source.zod.ts — exit 1, zero hits.
  • The file exists at that sha (git cat-file -e succeeds), so the zero is about content, not absence.
  • Positive control lit: the same search over packages/spec/src returns hits in 24 files. The token is a single bare word that cannot wrap across lines.

The real precedent, and the site I adopted. Eleven declarations across packages/spec/src carry one uniform prefix. I took packages/spec/src/ai/agent.zod.ts as the model — same ai/ directory, four uses, and named by no in-flight claim. Exact spelling adopted, character for character:

[EXPERIMENTAL — not enforced]

followed by the original description sentence, then a sentence naming what does not happen at runtime — e.g. agent.zod.ts:197 reads '[EXPERIMENTAL — not enforced] State machine defining the agent conversation flow and constraints. Parsed but no runtime consumer yet.'

ai/tool.zod.ts uses the identical spelling and was read only, never edited — PR #16531 holds that file.

Why the marking is true

PromptTemplateSchema has no consumer outside packages/spec. Measured on this branch: the only matches outside that package are a changeset, two generated docs pages, and the D7 ledger row itself. Nothing interpolates the holes; nothing checks the declared variables against them.

Verification

Clause-② gates, confirmed green and proven live. Both were run with no rebuild between the mutation and the gate, because a rebuild regenerates the gate's own baseline and returns a falsely green leg.

leg mutation predicted measured
A1 drop PromptTemplateSchema (const) from api-surface/ai.json red exit 1public API changed: 0 breaking (removed/narrowed), 1 added.
A2 restore green exit 0public API surface + factory signatures unchanged ✓
B1 drop ai/PromptTemplate:user from authorable-surface/ai.json red exit 1authorable-surface/ is out of date (1 key(s) not recorded). + ai/PromptTemplate:user
B2 restore green exit 0authorable-defaults/ verified against upstream 13c08356abc6 — 1211 default(s) unchanged

Each mutation leg carried an on-disk landing proof (anchored token count before and after) and each restore leg was proven by an empty git diff HEAD plus a git hash-object match against the HEAD blob. The script restores under a trap on EXIT INT TERM with absolute paths. The working tree is clean afterwards.

A first attempt at leg A1 was a genuine no-op and is reported rather than quietly retried: the anchor PromptTemplateSchema does not appear as a list element — the element is PromptTemplateSchema (const). The landing proof caught it (count before=0, after=0), the script aborted the leg as void, and the anchor was corrected before re-running. No green was banked from the void leg.

Test and gate runs (all at commit 9b3cb7461c):

  • pnpm --filter @objectstack/spec test484 files, 13149 tests passed
  • pnpm --filter @objectstack/spec typecheck — exit 0
  • pnpm --filter @objectstack/dogfood exec vitest run test/expression-conformance.test.ts4 passed (the D7 ledger and its ratchet)
  • pnpm --filter @objectstack/spec check:generatedall 15 generated artifacts up to date
  • pnpm check:nul-bytes, check:empty-changeset, check:changeset-no-major, check:adr-0087-registration, check:changeset-fixed, check:spec-changes, check:meta-url-spelling — all exit 0

The last three (check:changeset-fixed, check:spec-changes, check:meta-url-spelling) were run specifically because dispatch-gates.mjs flagged their rosters as living in directories my paths touch, where silence is evidence in neither direction.

Generated-artifact footprint, measured — 4 files, only 1 of them visible to git status:

tree files git-visible
content/docs/references/ai/model-registry.mdx 1 tracked
packages/spec/json-schema/ (ai/ModelRegistry.json, ai/PromptTemplate.json, objectstack.json) 3 gitignored

Counting only what git status reports would undercount the regeneration by the whole json-schema/ directory. Verified by content grep, not by a snapshot diff — the snapshot was taken after the build had already written that tree, so a diff alone would have reported a false zero.

Base

Branched from the literal sha 13c08356abc61f13acd5db0932f99d482c134588, and every proof above is anchored to it. origin/main has moved since; the branch is deliberately not merged forward so the anchoring holds, and the merge queue rebuilds against current main at land time.

Left as draft, not enqueued — that call is the PM's.

Generated by Claude Code


Generated by Claude Code

…rced]

Under the #15954 ruling (decision batch #56, option B) the template-typed
pair is marked, not retired. Both .describe() strings on PromptTemplateSchema
now carry the repo's existing [EXPERIMENTAL — not enforced] prefix and state
that no runtime renders or executes the template today.

The model site is ai/agent.zod.ts (four uses of the identical prefix); the
card named ai/knowledge-source.zod.ts, which carries no such marking at all
(zero hits for the bare token EXPERIMENTAL, positive control lit).

Prose only. .user remains required and .system remains optional — the schema
shape is untouched. The ADR-0058 D7 ledger row `template-prompt` records the
marking for both positions it covers, and the generated reference page is
regenerated with the repo's own tooling.

Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the size/s label Sep 7, 2026
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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 — 130 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 d1c86a745a42b15e0cfc2e61884a6672dfef3706packageMentionDocs.

Which tree this was computed on

This run read content/docs from 0ecd938b2e8d1ef4e61c8cf61bde929b726269e4 — the merge of head f6ddb40574fbbbd0eb9d0232c8d3562eb96b2204 into base d1c86a745a42b15e0cfc2e61884a6672dfef3706, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 0ecd938b2e8d1ef4e61c8cf61bde929b726269e4 && git checkout 0ecd938b2e8d1ef4e61c8cf61bde929b726269e4
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d1c86a745a42b15e0cfc2e61884a6672dfef3706 f6ddb40574fbbbd0eb9d0232c8d3562eb96b2204 && git checkout -B drift-repro d1c86a745a42b15e0cfc2e61884a6672dfef3706 && git merge --no-ff f6ddb40574fbbbd0eb9d0232c8d3562eb96b2204

node scripts/docs-audit/affected-docs.mjs --json d1c86a745a42b15e0cfc2e61884a6672dfef3706

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Copy link
Copy Markdown
Collaborator Author

Why this PR has been sitting blocked and out of the queue since 08:34Z — and a correction to my own reading

Lint & Repo Gates is failure (job 101671038335, completed 08:34:53Z), and it has been since seventeen minutes before I armed this PR at 08:51Z. It is not a flake, not an infra failure, and not a base-branch failure: it is this diff's own change, caught by the gate that exists for it.

The failing gate, exactly

pnpm check:doc-authoring → the cross-package prose-id leg (#13297), exit 1:

✗ NEW internal issue-id reference(s) in sibling-package string prose:

  packages/qa/dogfood/test/expression-conformance.ledger.ts  #15954  (0 pinned, 1 measured)
    :385  'EXPERIMENTAL — declared prompt templates with no runtime evaluator (ADR-0049),
           and MARKED as such at the declaration under the #15954 ruling (decision batch #5…

This is item 2 of the PR body — the D7 ledger row that records the marking. The ruling's tracker id went into a runtime string, and the gate's whole subject is that a runtime string reaches authors, operators and generated surfaces, none of whom can resolve #NNNN.

Note what the gate did not object to: ADR-0049 in the same sentence is fine and must stay. The gate names that class explicitly — "Keep customer-resolvable references (an ADR id, a protocol version, an error code) — only the tracker id goes."

The remedy is the gate's own, and there is exactly one

Strip #15954 (and the decision batch #5… id if it is in the same string) out of the string literal and put it in an adjacent // comment. The reader who can resolve a tracker id is reading the source; git history keeps the anchor either way.

The baseline file is not an option here. scripts/doc-authoring-prose-id.baseline.json pins the adjudicated pre-#13297 population and is shrink-only; the gate says in terms that adding an entry is MAINTAINER-ONLY and not a co-equal path to green. Nobody on this card takes that path.

Correction — my arming call was made on a partial reading

I armed this PR at 08:51Z on the basis that its checks were green. They were not, and the way I got it wrong is worth naming, because it is the same error shape twice in this lane now:

The check-run listing for this PR holds 35 runs and pages at 30. Lint & Repo Gates is number 35 — it sits alone on page 2. I read page 1, saw thirty successes and skips, and called it green. A first page of 30 out of 35 is not a reading of 35. This is the identical mistake as reading a truncated list_issues page and calling it a census of the queue: in both cases the tool told me how many there were, and in both cases I did not check that against how many I had actually looked at.

The rule I am adopting from it: when a listing reports a total, compare that total against the number of rows in hand before drawing any conclusion from it — and for check runs specifically, page until the count is reached, since the one failure in a set of 35 is exactly the row that will be last.

State

Returned to draft, because "not draft" in this lane means armed, and armed is currently false. The repair is queued as this seat's next dev dispatch — ahead of any new card — and is held only by the standing concurrency cap of 3, which is currently full. It is one string edit plus a comment; nothing about the schema change, the ablation legs or the api-surface evidence in this PR is affected by it, and none of that needs re-running.


Generated by Claude Code

…s runtime string

`check:doc-authoring`'s cross-package prose-id leg reds on a tracker id inside
a non-test string under packages/: the ledger `note` is runtime prose that
reaches authors, operators and generated surfaces, none of whom can resolve
one. The ruling id and its decision-batch number now live in an adjacent `//`
comment — the gate's own prescribed remedy; ADR-0049 stays in the string as a
customer-resolvable reference. No baseline entry: that file is maintainer-only
and shrink-only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno
@huangyiirene
huangyiirene marked this pull request as ready for review September 7, 2026 11:41

Copy link
Copy Markdown
Collaborator Author

Repaired and armed — Lint & Repo Gates is green on f6ddb40574

The failure this PR sat on is fixed. check:doc-authoring's cross-package prose-id leg now passes:

✓ doc authoring guard: sibling-package prose ids hold the baseline — 829 pinned site(s) across 231 file(s), 86845 string(s) read in 1169 parsed source(s), no growth, no burn-down unrecorded.

The repair found more than the log showed. The log named one (file, id) pair, #15954. The string actually carried two tracker-shaped ids — #15954 and the #56 of "decision batch #56". The gate's id regex matches 3–5 digits, so #56 was never going to be reported; it would have sat there as a second latent finding the moment that regex widened. Both came out of the literal and now live in an adjacent // comment. ADR-0049 stayed in the string, as the gate's own rule requires.

⛔ No entry was added to scripts/doc-authoring-prose-id.baseline.json — that path was refused as maintainer-only, which is what the gate demands.

The verification the earlier red run could not give

The dev flagged, correctly, that the previous red Lint & Repo Gates job may have stopped at the doc-authoring step, so every step after it in that job had never been measured on this branch at all — a green from the repair alone would not have covered them. That is now settled by CI rather than by argument:

All 34 check runs read, all success or skipped, Lint & Repo Gates among them (completed 10:20:59). I read the full listing, not its first page — on this PR's previous head the sole failure was run #35 of 35, alone on page 2, and reading page 1 is exactly how I armed it red this morning.

Scope held

One file changed by the repair (packages/qa/dogfood/test/expression-conformance.ledger.ts), +3/−1, with the reproduce-then-fix pair done in the right order: check:doc-authoring exit 1 on the unrepaired tree naming this file and id, then exit 0 after. On-disk landing proof carried (blob 0994f6828a86035776cf; old fragment 1→0, new 0→1, comment 0→1).

The D7 ratchet still reports 4 passed — and the dev explained why it could not have moved rather than just reporting the number: the suite asserts through checkLedger on id/mode/dialect/failPolicy and the covers positions, never on note text. check:generated 15/15 with a clean tree afterwards, so no generated artifact moved; expected, since the edited string is a dogfood ledger note, not a spec .describe().

main was not merged forward — the merge-base is still 13c08356ab, so every proof in the PR body remains anchored where it was taken. The schema marking, the generated reference page and the changeset are untouched.

⚠️ One known-stale line, deliberately left: the PR body still reads "Under the #15954 ruling (decision batch #56, option B)". That is PR prose, not a runtime string — the gate scans repo files, not PR bodies — and every other statement in the body remains true at its anchor. Recording it here rather than editing the body, so the anchoring the body depends on is not disturbed for one sentence.

Ready flipped, auto-merge on.


Generated by Claude Code

@huangyiirene
huangyiirene added this pull request to the merge queue Sep 7, 2026
Merged via the queue into main with commit f4e6adf Sep 7, 2026
36 checks passed
@huangyiirene
huangyiirene deleted the claude/issue-16321-prompt-template-experimental-marking branch September 7, 2026 12:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ai size/s tooling

Projects

None yet

2 participants