Skip to content

docs(openspec): propose a runtime demo path - #235

Merged
thecodedrift merged 8 commits into
mainfrom
openspec/runtime-demo-path
Sep 2, 2026
Merged

docs(openspec): propose a runtime demo path#235
thecodedrift merged 8 commits into
mainfrom
openspec/runtime-demo-path

Conversation

@thecodedrift

@thecodedrift thecodedrift commented Sep 1, 2026

Copy link
Copy Markdown
Member

Proposal only — no implementation, and the tasks are deliberately blocked on the service half.

Why

The runtime tier cannot be demonstrated, and it cannot be smoke-tested end to end. Discovery, signing, validation and writing are ours; generation is the service's. The handshake between them belongs to neither and is tested by neither.

That is not abstract. Every defect the payload alignment work surfaced lived in that seam and was found by a person noticing: a runtime rule below the file-set floor admitted rather than withheld, a signature attached to the wrong rule, a capture dropped for want of a name, a rule written and never executed. An empty scan reports success, so the symptom was almost always "no findings" rather than an error.

The shape

Two endpoints mirroring the mainline, asked of the generator team as N9:

POST /cli/api/demo/request                 -> { requestId, status }
GET  /cli/api/demo/request/{requestId}     -> { requestId, status, rules[] }

Mirroring is the decision, not a convenience. A bespoke demo payload would exercise a path no user is on, which is the one thing a demo must not do. Reusing the well-known formats means submitRule, pollRuleStatus and writeRuleFile are reused rather than reimplemented — so the demo covers polling and the terminal failed/unsupported states for free, because it is the same client code reaching them. If the demo ever needs its own writer, that is evidence the shapes have diverged, and the demo has done its job by failing.

Two things written down because they are the tempting ones to get wrong

It stops at verify, and says so. A runtime rule executes only when an authenticated reconcile returns its signature in run. The demo is unauthenticated, so it reaches a complete, verified rule on disk and no further — check already explains that state.

Three options existed. Stopping at verify is honest. --dangerously-run-scripts reaches execution under the warning it always prints. A client-side bypass keyed on the demo rule id is refused, and the spec carries that as a requirement rather than a comment: the signature gate is the only thing between a payload fetched over the network and arbitrary code running on a developer's machine, and the identifier it would key on is visible to anyone who reads the recipe.

Hiding the topic needs no mechanism. RECIPE_TOPICS in commands/agent.ts is a hand-maintained literal and getRecipe looks up by filename, so a recipe absent from the list is already fetchable by name and invisible to the index. cli-agent gains a requirement stating that is intended, so a later reader does not treat it as an oversight and close it.

And the command nests rather than registering. taskless agent prints two independently-sourced listings, and only the second is RECIPE_TOPICS; the first iterates the top-level command tree and filters just agent itself. A new top-level verb would therefore be hidden as a recipe and advertised as a command in the same output. The demo is rule demo, a subcommand of the existing rule command, so SUBCOMMAND_NAMES and subCommands are untouched and no filtering code has to be written (design D4a).

Deliberately blocked

Task group 0 is the service half. Nothing below it starts until the endpoint shapes are confirmed, because building against a guessed shape is what produced the seam this change exists to close.

N10 has since landed. ruleId named a ticket rather than a rule — by the service's own field description, "The ticket id the rules were generated under." The generator team renamed the resource to request/requestId, and GET /cli/api/__schema now shows /cli/api/request/* as canonical with the rule/* family deprecated: true while still serving. The demo takes the settled noun, which is why the paths above read request. Adopting it in the ordinary client is a separate change and is not this one's dependency. The demo must never be where a second convention debuts.

Verification

openspec validate runtime-demo-path passes, 4/4 artifacts complete. Lint and pnpm cli check clean (4 pre-existing no-hedging warnings in route.txt/onboard.txt, untouched by this change).

@thecodedrift

Copy link
Copy Markdown
Member Author

@claude /review

@claude

claude Bot commented Sep 1, 2026

Copy link
Copy Markdown

Claude finished @thecodedrift's task in 5m 19s —— View job


Review complete

Review mode: incremental — read 0 prior review thread(s) before reviewing. .prior-review.json contains no threads, reviews, or resolved comments (only the trigger comment @claude /review itself), so this is the first review of this PR; the whole diff was assessed.

  • Read .prior-review.json
  • Gather PR diff and metadata (gh pr diff, gh pr view)
  • Read changed OpenSpec artifacts in full
  • Cross-check referenced source files (api/rules.ts, api/client.ts, commands/agent.ts, commands/names.ts, index.ts, check.ts, rules/files.ts, base cli-agent spec)
  • Post review findings

This is a proposal-only change (no implementation), so the review focused on internal consistency of the OpenSpec artifacts and whether their claims hold up against the current codebase. Posted 3 inline comments:

  1. design.md D2 contradicts D1 (lines 60-73): D2 describes the demo paths as mirroring "today's /cli/api/rule" naming and frames the request/requestId rename as still pending — but D1 immediately above it, and tasks.md 0.0/0.0a, already reflect the rename as landed and use requestId/request. This reads as leftover text from before the later "the rename landed" commits and should be updated so D1 and D2 agree on which naming is current.

  2. The "reuse submitRule/pollRuleStatus, not reimplement" claim doesn't match the current code (proposal.md lines 79-80, mirrored in design D1 and tasks 1.2): both functions in packages/cli/src/api/rules.ts are hardcoded to /cli/api/rule and /cli/api/rule/{ruleId} (not the demo's /cli/api/demo/request*), and both require a token: string that createApiClient turns unconditionally into an Authorization: Bearer header, conflicting with the demo's "no login" goal. The proposal also says adopting the rename in the ordinary client is explicitly out of scope for this change, so these functions won't move to the new paths as a side effect either. Reuse as literally described isn't possible without a refactor that isn't in tasks.md.

  3. cli-agent spec's "hidden by omission" requirement only covers the recipe-topic index (specs/cli-agent/spec.md lines 5-7): taskless agent's printed index has a second section built from registered top-level subcommands (commands/agent.ts iterating subCommands from index.ts, gated by commands/names.ts). If the demo needs a dispatchable command, nothing in this change says how it avoids appearing there too — a different mechanism than RECIPE_TOPICS, which is the only thing D4/this requirement addresses.

Minor, not inlined:

  • tasks.md 0.2 is still unchecked ("confirm the demo is servable unauthenticated... a 'no' is a complete answer") while design.md D3 and the cli-runtime-demo spec already assert unauthenticated operation as settled fact. Probably fine given the task's own "changes nothing below" caveat, but worth double-checking that framing holds if the answer does come back "no."
  • The PR description above still shows POST /cli/api/demo/rule -> { ruleId, status }, while the committed files (after the later "the rename landed" commits) use /cli/api/demo/request / { requestId, status }. Worth updating the PR body so it doesn't mislead someone skimming just the description.

No blocking issues beyond the internal-consistency points above — the surrounding design reasoning (stopping at verify, no client-side bypass, failure-not-fabrication) checks out against check.ts's actual --dangerously-run-scripts/signature-gate behavior.

Comment thread openspec/changes/runtime-demo-path/design.md Outdated
Comment thread openspec/changes/runtime-demo-path/proposal.md Outdated
Comment thread openspec/changes/runtime-demo-path/specs/cli-agent/spec.md
@thecodedrift
thecodedrift force-pushed the openspec/runtime-demo-path branch from a704a09 to 31a04a0 Compare September 2, 2026 00:23
@thecodedrift

Copy link
Copy Markdown
Member Author

Re: @claude[bot] — "Claude finished @thecodedrift's task in 5m 19s — Review complete. Review mode: incremental…"
#235 (comment)

Addressed in 31a04a0. Taking the three findings in order:

1. design.md D2 contradicts D1 — fixed. It was leftover pre-rename text. D2 now leads with the principle (the demo mirrors the mainline's naming, whatever it currently is, and is never where a convention debuts), then records the rename as history rather than as pending, and names the skew that actually remains: the demo is born on request/requestId while the ordinary client stays on the rule/* family, which still serves marked deprecated. The stale Risks entry ("A rename lands first, and this waits for it") was rewritten to describe that skew instead.

2. The submitRule/pollRuleStatus reuse claim — verified as a real gap, deliberately left for the author to rule on. You are right on the facts: both are hardcoded to /cli/api/rule and /cli/api/rule/{ruleId}, both take a required token that createApiClient turns unconditionally into a Bearer header, and the proposal does put adopting the rename in the ordinary client out of scope. This one landed in the low bucket, so it is being reported to the author with a recommendation rather than resolved unilaterally — it touches the D1 "reuse, not reimplement" decision, and D1's own logic ("if the demo needs a new writer, the shapes have diverged") makes the right answer a judgement call rather than an edit. Nothing in this commit pre-empts it; that thread stays open.

3. cli-agent only covers RECIPE_TOPICS — fixed, and it was the sharpest finding here. Confirmed against commands/agent.ts: the Topics: block iterates Object.entries(subCommands) and filters exactly one name, agent itself, never consulting RECIPE_TOPICS. A demo registered as a new top-level verb would have been hidden as a recipe and advertised as a command in the same output. Settled by placement rather than by a filter — new design D4a makes the command rule demo, nested under the existing rule command, so SUBCOMMAND_NAMES and subCommands are untouched. cli-agent gains a requirement that the index has two independently-sourced listings; cli-runtime-demo gains a normative requirement that the demo is not registered top-level; tasks 2.0 and 2.5 cover it.

Both minor items also taken. Task 0.2 now separates "can the service serve this unauthenticated" (open) from what the demo does either way (settled by D3 — it stops at verify), and says which answer would actually reopen the design. The PR description has been updated; it was still showing POST /cli/api/demo/rule -> { ruleId, status }.

Grepped the whole change directory afterwards for demo/rule, ruleId, status and the old "repository the service controls" phrasing — none remain. pnpm openspec validate runtime-demo-path passes, pnpm lint is clean, pnpm cli check shows only the 4 pre-existing no-hedging warnings in route.txt/onboard.txt, and the CLI suite is 1169/1169.

— AI Coding Agent

thecodedrift added a commit that referenced this pull request Sep 2, 2026
Review feedback on #235. D1 claimed `submitRule`, `pollRuleStatus` and
`writeRuleFile` are "reused, not reimplemented", and for two of the
three that is not true yet.

Both requesters are hardcoded to the rule-family endpoints and take a
required `token`, which `createApiClient` turns unconditionally into an
`Authorization: Bearer` header. The demo is unauthenticated by
requirement, so neither is usable as written. The path half resolves
itself when the client adopts the renamed family; the token half does
not, and is the durable part.

The reason this is worth more than the wording: D1's failure test says
"if the demo needs a new writer, the shapes have diverged, and the demo
has done its job by failing". That test is a claim about PAYLOAD SHAPES,
and it was already tripping — for the requester and the poller, where it
means nothing of the kind. A requester needing a second argument says
nothing about whether the demo and the mainline agree on what a rule
looks like.

So D1 now splits them. `writeRuleFile` is byte-for-byte reuse and is
what the failure test is really about. The other two are two small
changes to functions written when every caller was authenticated, and
they are planned as task 1.2a rather than discovered mid-implementation,
which is what the previous wording set up.

1.2b is the part nobody raised: an unauthenticated call must send no
`Authorization` header rather than one carrying an empty token. A header
the service then has to decide how to ignore is a worse contract than
its absence.
The runtime tier cannot be demonstrated or smoke-tested end to end.
Everything we can exercise alone covers the pieces; nothing covers the
two things that exist only between this client and the service, which
are generation producing a runtime rule and delivery handing it over in
a shape we accept. Every defect the alignment work surfaced lived in
that seam and was found by someone noticing.

The shape, as asked of the generator team in N9: two endpoints mirroring
the mainline, a ticket then a retrieval, returning the published
file-set variant. Mirroring is the decision rather than a convenience —
a bespoke demo payload would exercise a path no user is on, which is the
one thing a demo must not do, and reusing the well-known formats means
the demo covers polling and the terminal states because it is the same
client code reaching them.

Two things the design writes down because they are the tempting ones to
get wrong.

The demo stops at `verify`, and says so. A runtime rule executes only
when an authenticated reconcile returns its signature, and the demo is
unauthenticated, so it reaches a complete verified rule on disk and no
further. A client-side bypass keyed on the demo rule id would show the
real thing and is refused: the signature gate is the only thing between
a payload fetched over the network and arbitrary code running on a
developer's machine, and the id it would key on is visible to anyone who
reads the recipe. The spec carries that as a requirement rather than a
comment.

Hiding the topic needs no mechanism. `RECIPE_TOPICS` is a hand-maintained
literal and lookup is by filename, so a recipe absent from the list is
already fetchable by name and invisible to the index. `cli-agent` gains
a requirement saying that is intended, so nobody later reads it as an
oversight and closes it.

Tasks are blocked on the endpoints existing. Building against a guessed
shape is what produced the seam this change is closing.
The generator team is renaming the request resource to
`request`/`requestId` as its own change, ahead of this one. Their
`openspec/specs/cli/spec.md` already specified that noun, and their live
`meta` block already carries `ticketId` per delivered rule, so the route
was the outlier rather than the convention.

D2 said the demo must never be where a second convention lives. That was
a principle; the rename makes it a sequencing constraint. Naming these
endpoints today would make the demo the debut of the new noun, which is
the thing D2 rules out. Task 0.0 waits for the rename and the regenerated
schema, and nothing starts before it.

Also corrects a premise the generator team refuted. This proposal said
generation runs against "a public repository the service controls". It
does not: CLI-bound generation never clones, the clone is deferred to the
git-bound push that only happens for a pull request, and `repositoryUrl`
on a CLI request is authorization scoping rather than model input. The
fixed input is a prompt and its examples held as data. Recorded as D2a,
because the wrong version was specific enough to be believed, and a later
reader would have tried to make the repository load-bearing.
Task 0.0 waited for `request`/`requestId` to become canonical. It has:
verified from `GET /cli/api/__schema` that `/cli/api/request/*` is
canonical and the `rule/*` family is `deprecated: true` while still
serving.

So the demo endpoints are `POST /cli/api/demo/request` and
`GET /cli/api/demo/request/{requestId}`. That was the whole reason for
holding this change behind the rename: naming them earlier would have
made the demo the place a new convention debuted, which is what D2 rules
out. The mainline took the noun first and the demo follows it.

Adopting the renamed paths in the ordinary client is its own change and
is deliberately not a dependency of this one. The legacy family still
serves, so the two can land in either order.
The previous commit renamed the endpoints in the proposal and the task
list header and left two references behind, in task 0.1 and in design
D1. Both still said `demo/rule` and `ruleId`.

Recording it rather than folding it in silently, because it is the exact
failure this change is about: the noun moved in one place and not in the
places describing it, and nothing measured the gap. Grepped for the old
spelling afterwards this time.

0.1 is also checked off — the shapes are agreed on both sides.
D2 still framed the request rename as pending while D1 and tasks 0.0/0.0a
already recorded it as landed, so a reader met a contradiction about which
naming is current. D2 now states the principle, then the rename as history,
and the Risks entry describes the skew that actually remains: the demo is born
on request/requestId while the ordinary client stays on the deprecated family.

The cli-agent requirement also only covered RECIPE_TOPICS, but `taskless agent`
prints two independently-sourced listings and the other one iterates the
top-level command tree, filtering only `agent` itself. Nothing said where the
demo command lives, so a top-level verb would have been hidden as a recipe and
advertised as a command in the same output. D4a settles it as `rule demo`,
nested under the existing rule command, obtaining the property by not
registering rather than by teaching a renderer to skip.
Review feedback on #235. D1 claimed `submitRule`, `pollRuleStatus` and
`writeRuleFile` are "reused, not reimplemented", and for two of the
three that is not true yet.

Both requesters are hardcoded to the rule-family endpoints and take a
required `token`, which `createApiClient` turns unconditionally into an
`Authorization: Bearer` header. The demo is unauthenticated by
requirement, so neither is usable as written. The path half resolves
itself when the client adopts the renamed family; the token half does
not, and is the durable part.

The reason this is worth more than the wording: D1's failure test says
"if the demo needs a new writer, the shapes have diverged, and the demo
has done its job by failing". That test is a claim about PAYLOAD SHAPES,
and it was already tripping — for the requester and the poller, where it
means nothing of the kind. A requester needing a second argument says
nothing about whether the demo and the mainline agree on what a rule
looks like.

So D1 now splits them. `writeRuleFile` is byte-for-byte reuse and is
what the failure test is really about. The other two are two small
changes to functions written when every caller was authenticated, and
they are planned as task 1.2a rather than discovered mid-implementation,
which is what the previous wording set up.

1.2b is the part nobody raised: an unauthenticated call must send no
`Authorization` header rather than one carrying an empty token. A header
the service then has to decide how to ignore is a worse contract than
its absence.
thecodedrift added a commit that referenced this pull request Sep 2, 2026
Review feedback on #235. D1 claimed `submitRule`, `pollRuleStatus` and
`writeRuleFile` are "reused, not reimplemented", and for two of the
three that is not true yet.

Both requesters are hardcoded to the rule-family endpoints and take a
required `token`, which `createApiClient` turns unconditionally into an
`Authorization: Bearer` header. The demo is unauthenticated by
requirement, so neither is usable as written. The path half resolves
itself when the client adopts the renamed family; the token half does
not, and is the durable part.

The reason this is worth more than the wording: D1's failure test says
"if the demo needs a new writer, the shapes have diverged, and the demo
has done its job by failing". That test is a claim about PAYLOAD SHAPES,
and it was already tripping — for the requester and the poller, where it
means nothing of the kind. A requester needing a second argument says
nothing about whether the demo and the mainline agree on what a rule
looks like.

So D1 now splits them. `writeRuleFile` is byte-for-byte reuse and is
what the failure test is really about. The other two are two small
changes to functions written when every caller was authenticated, and
they are planned as task 1.2a rather than discovered mid-implementation,
which is what the previous wording set up.

1.2b is the part nobody raised: an unauthenticated call must send no
`Authorization` header rather than one carrying an empty token. A header
the service then has to decide how to ignore is a worse contract than
its absence.
@thecodedrift
thecodedrift force-pushed the openspec/runtime-demo-path branch from 4fd79ff to 4e6b068 Compare September 2, 2026 03:27
The generator team settled the demonstration as account-free: no ticket, no
corpus entry, no billing, no authentication, one Taskless-owned installation
behind every request. Three things follow.

The endpoints take no auth at all rather than an ignorable empty token, so the
header ask is a property instead of a convention. Retrieval also serves what
the check found against the fixture's examples, which the verification gate was
already computing and discarding.

D3 said logging in was "the next beat" to reach a blessed execution. That is
wrong: blessing is recording, and a fixed rule shared by every caller is never
recorded for any of them, so authenticating reaches nothing. The gate is
unreachable by construction from a shared fixture rather than one step away.
Exercising the handshake needs a rule recorded for the org that runs it, which
is an integration test in an org we own, not a demo.

D6 takes the findings as `Finding[]` verbatim, grouped per example with what
that example was expected to do. `Finding` is our own runtime-rule type, so a
narrower demo-only object would be the bespoke shape D1 forbids. The grouping
is what carries the demonstration: a flat list renders identically for a rule
that catches the failing examples and one that fires on everything, and the
second is this area's recurring defect wearing a success costume.

D7 states what the negative framing obscures. The rule is real and inspectable;
it is inert because no signature for it is in any org's run set, which is the
gate working on the ordinary path; it is removable through the delete flow we
own; and improve refuses it, correctly, because nothing pre-generated can match
a rule authored against the developer's own repo. Tasks 3.4 and 5.2 pin the two
that would fail silently.
@thecodedrift
thecodedrift force-pushed the openspec/runtime-demo-path branch from 4e6b068 to b3f5e50 Compare September 2, 2026 03:27
@thecodedrift

Copy link
Copy Markdown
Member Author

@claude /review

@claude

claude Bot commented Sep 2, 2026

Copy link
Copy Markdown

Claude finished @thecodedrift's task in 5m 20s —— View job


Review complete

Review mode: incremental — read 3 prior review thread(s) before reviewing. All 3 are resolved with *— AI Coding Agent* closing replies, so all were treated as ADDRESSED and not repeated:

  1. D2 contradicting D1 on which naming (rule vs request) is current — fixed in 31a04a0 (D2 rewritten to lead with the principle and record the rename as history).
  2. submitRule/pollRuleStatus reuse not matching the code (hardcoded paths + required token) — fixed in 4fd79ff (D1 split into three parts: writeRuleFile is the real reuse claim, the requester/poller need task 1.2a/1.2b first).
  3. cli-agent spec only covering the RECIPE_TOPICS half of the printed index, not the top-level-command half — fixed (new D4a, rule demo nests under the existing rule command instead of registering top-level; cli-agent and cli-runtime-demo specs both gained requirements for it).

I re-verified all three against the current files and current source (commands/agent.ts, commands/rules.ts, commands/names.ts, index.ts) — the fixes hold up and match the code.

3 new findings, posted inline:

  1. design.md's "skew" premise is stale (Risks section, ~lines 274-280, and D1 ~lines 71-72): the claim that "the ordinary client is still on rule/ruleId" doesn't match packages/cli/src/api/rules.ts / restore.ts, which already target /cli/api/request everywhere — a repo-wide grep for the literal /cli/api/rule path (outside generated/) finds nothing. The rename this section frames as still-pending for the mainline client appears to have already landed there.

  2. proposal.md's endpoint contract disagrees with design.md/tasks.md on whether examples[] is confirmed (proposal.md lines 39-42 vs. design.md D1 lines 43-44 and tasks.md 0.1/0.4): proposal.md states examples[] as part of the settled two-endpoint shape; design.md D1's own decision text and the checked-off task 0.1 give the shape without it, and task 0.4 (telling the service the findings shape) is still unchecked.

  3. D7's assumption that improving the demo rule returns RULE_NOT_FOUND isn't confirmed with the service, and the client's own error-handling code suggests the opposite is at least as likely (design.md ~lines 257-264, mirrored in cli-runtime-demo/spec.md's improve requirement): iterateRule distinguishes 404 request_not_found (→ CLIError RULE_NOT_FOUND) from 403 access_denied (→ generic Error, which improveCommand reports as NETWORK_ERROR). Since the demo request genuinely exists (just recorded under a different, Taskless-owned org), a real backend is arguably more likely to answer access_denied than request_not_found — which would report as NETWORK_ERROR, exactly the "tell an agent to retry an id that will never resolve" miscategorization iterateRule's own comment warns about. Not asked of the generator team in task group 0.

No other blocking issues — the rest of the internal-consistency and codebase cross-checks (D3/D4/D4a placement claims, Finding type export, check.ts's skip-reason strings, writeRuleFile's capture-refusal validation) held up against current source.
· branch openspec/runtime-demo-path

Comment thread openspec/changes/runtime-demo-path/design.md Outdated
Comment thread openspec/changes/runtime-demo-path/proposal.md Outdated
Comment thread openspec/changes/runtime-demo-path/design.md
Review of the assembled change found three places where the documents assert
something that is not true, or not yet agreed.

**The skew premise was stale.** D1, D2 and the Risks entry described the
`request`/`requestId` rename as landed on the service and pending in our
client. It landed in our client too: `submitRule` posts to `/cli/api/request`,
`pollRuleStatus` gets `/cli/api/request/{requestId}`, `iterateRule` and
`restoreRule` follow, and no hand-written call site targets the `rule/*`
family. The reasoning stays and moves to past tense, and the Risks entry now
records what the move taught: the types caught none of it, because a deprecated
path is still in the OpenAPI document and type-checks as well as the canonical
one. A vendored document and `api-deprecated-paths.test.ts` are what close that
gap. D1's "the path half resolves itself" is corrected in place - it did not,
and the literal being a literal was always the real obstacle.

**The proposal overclaimed `examples[]`.** Its retrieval code block showed
`{ requestId, status, rules[], examples[] }` as settled while D1 and task 0.1
say `rules[]` and task 0.4 is open. The block is back to the agreed shape, with
a sentence saying the findings payload is an open ask. D6 keeps its reasoning
and gains a note that its shape is under revision.

**`RULE_NOT_FOUND` on `improve` was assumed.** `iterateRule` maps a 404
`request_not_found` to `CLIError(RULE_NOT_FOUND)` but a 403 `access_denied` to
a plain `Error`, which `improveCommand` reports as `NETWORK_ERROR`. The demo
request exists under a Taskless-owned installation and is not the caller's,
which is 403-shaped. D7 and the spec requirement now state the behaviour -
improvement refused, nothing written, and a terminal failure rather than one
inviting a retry - without asserting a code in either direction. New task 0.5
asks the service which status a foreign-org ticket returns; task 3.4 pins the
behaviour now and the code after.
@thecodedrift

Copy link
Copy Markdown
Member Author

Re: @claude[bot] — "Claude finished @thecodedrift's task in 5m 20s — Review complete. Review mode: incremental — read 3 prior review thread(s)… 3 new findings, posted inline…"
#235 (comment)

All three addressed in 76b3952, and all three were correct. Replies are on the inline threads; the short version:

1. The stale skew premise — verified, then rewritten to past tense. grep -rn '/cli/api/rule' packages --include='*.ts' outside generated/ finds only rule-hash-vectors and a test's own prose. 56d00a6 chore(api): call the renamed /cli/api/request paths is an ancestor of origin/main. The reasoning in D1, D2 and Risks stays — this document keeps its history deliberately — but two of its claims were wrong rather than merely stale, and both are now recorded as corrections. The Risks entry expected the types to catch the migration; they caught none of it, because a deprecated path is still IN the OpenAPI document and client.GET("/cli/api/rule/{ruleId}") type-checks as well as the canonical spelling. What closes that gap is a vendored document plus test/api-deprecated-paths.test.ts, which is now the standing note under D2 for the next rename the demo has to follow. And D1's "the path half resolves itself" could not have been true: those functions moved from one hardcoded literal to another, and the demo's endpoint is /cli/api/demo/request either way. The obstacle was never the noun; it was that the literal is not a parameter.

2. The examples[] overclaim — removed, not resolved. The code block is back to { requestId, status, rules[] }, matching D1 and the checked-off task 0.1, with one sentence saying the findings payload is an open ask tracked by 0.4. D6 keeps its reasoning and gains a note that its shape is under revision. This PR deliberately does not design that payload — the fix is only to stop the proposal asserting a contract N9 has not agreed to, which is exactly the sequencing risk you named.

3. RULE_NOT_FOUND — confirmed the mapping, then stopped asserting a code. Read both files: iterateRule throws CLIError(…, "RULE_NOT_FOUND") on 404 request_not_found and a plain Error on 403 access_denied; improveCommand falls back to NETWORK_ERROR for anything that is not a CLIError. By this change's own D3 reasoning the demo request exists and merely is not the caller's, which is 403-shaped. D7 and the spec requirement now state the behaviour — refused, nothing written, terminal rather than inviting a retry of the same identifier — and assert no code in either direction, since NETWORK_ERROR would be the same unconfirmed claim with the sign flipped. New task 0.5 asks the service which status a foreign-org ticket returns; task 3.4 pins the behaviour now and the code once 0.5 answers.

Checks: pnpm openspec validate runtime-demo-path --strict passes, and pnpm build && pnpm cli check reports the 4 pre-existing no-hedging warnings (3 in route.txt, 1 in onboard.txt) and nothing else. Still docs-only.

— AI Coding Agent

@thecodedrift
thecodedrift merged commit 8cd86fc into main Sep 2, 2026
5 checks passed
thecodedrift added a commit that referenced this pull request Sep 2, 2026
Review feedback on #235. D1 claimed `submitRule`, `pollRuleStatus` and
`writeRuleFile` are "reused, not reimplemented", and for two of the
three that is not true yet.

Both requesters are hardcoded to the rule-family endpoints and take a
required `token`, which `createApiClient` turns unconditionally into an
`Authorization: Bearer` header. The demo is unauthenticated by
requirement, so neither is usable as written. The path half resolves
itself when the client adopts the renamed family; the token half does
not, and is the durable part.

The reason this is worth more than the wording: D1's failure test says
"if the demo needs a new writer, the shapes have diverged, and the demo
has done its job by failing". That test is a claim about PAYLOAD SHAPES,
and it was already tripping — for the requester and the poller, where it
means nothing of the kind. A requester needing a second argument says
nothing about whether the demo and the mainline agree on what a rule
looks like.

So D1 now splits them. `writeRuleFile` is byte-for-byte reuse and is
what the failure test is really about. The other two are two small
changes to functions written when every caller was authenticated, and
they are planned as task 1.2a rather than discovered mid-implementation,
which is what the previous wording set up.

1.2b is the part nobody raised: an unauthenticated call must send no
`Authorization` header rather than one carrying an empty token. A header
the service then has to decide how to ignore is a worse contract than
its absence.
@thecodedrift
thecodedrift deleted the openspec/runtime-demo-path branch September 2, 2026 04:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant