Skip to content

Commit 2d2bc74

Browse files
committed
docs(openspec): archive runtime-entitlement-withheld
1 parent daaa7db commit 2d2bc74

13 files changed

Lines changed: 148 additions & 34 deletions

File tree

openspec/changes/runtime-entitlement-withheld/.openspec.yaml renamed to openspec/changes/archive/2026-09-27-runtime-entitlement-withheld/.openspec.yaml

File renamed without changes.

openspec/changes/runtime-entitlement-withheld/design.md renamed to openspec/changes/archive/2026-09-27-runtime-entitlement-withheld/design.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,18 +69,18 @@ so `--json` consumers reading `skipped` alone still see the right cause.
6969
entitlement warning is one more entry in each, so no new output channel and no
7070
schema change beyond `check`'s `entitlement` field.
7171

72-
The restore response is typed from the generated schema, which does not yet
73-
carry `entitlement`. `restore.ts` reads it off the parsed body with the same
74-
normalizer as reconcile rather than widening the generated type by hand; the
75-
schema refresh that follows #207's deploy replaces the defensive read with the
76-
typed field.
72+
The restore and retrieval responses are typed from the generated schema, which
73+
does not yet carry `entitlement`. They are widened with `MayCarryEntitlement<T>`
74+
(the field as optional `unknown`) and read through the same normalizer as
75+
reconcile, so the CLI ships assuming the field MIGHT be there instead of waiting
76+
on #207's deploy. Tightening that once the service always sends it is #409.
7777

7878
## Risks
7979

8080
- **A server bug that lists a file in `withheld` for an entitled org fails CI.**
8181
Accepted: failing closed is the property runtime rules exist for, and the
8282
failure names its cause and the URL to check.
8383
- **The vendored schema is stale in an unrelated way.** A refresh on 2026-09-27
84-
also changes `successCases`/`failureCases` and drops `signature` from several
85-
file-set responses. Out of scope here; noted so the follow-up regeneration is
86-
reviewed rather than accepted wholesale.
84+
also changes `successCases`/`failureCases` and drops `signature` from the
85+
`sg`/`vale` file-set variants (the `runtime` variant keeps it). The cloud will
86+
restore those signatures; regeneration waits for that (#409).

openspec/changes/runtime-entitlement-withheld/proposal.md renamed to openspec/changes/archive/2026-09-27-runtime-entitlement-withheld/proposal.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ Nothing changes for unauthenticated or `--anonymous` runs, for
8383
condition, which a CI recipe must not read as a findings failure.
8484
- `api.schema.json` / `api.d.ts`: **not** regenerated here. #207 is not
8585
deployed; the live `__schema` carries no `entitlement` today (measured
86-
2026-09-27). Restore and retrieval read the field off the body defensively
87-
until it is, and a follow-up task regenerates once it ships.
86+
2026-09-27). Restore and retrieval type it as a field that might be present
87+
(`MayCarryEntitlement<T>`); tightening once it always is, is #409.
8888

8989
## Delivery shape
9090

openspec/changes/runtime-entitlement-withheld/specs/cli-check/spec.md renamed to openspec/changes/archive/2026-09-27-runtime-entitlement-withheld/specs/cli-check/spec.md

File renamed without changes.

openspec/changes/runtime-entitlement-withheld/specs/cli-generated-rule-delivery/spec.md renamed to openspec/changes/archive/2026-09-27-runtime-entitlement-withheld/specs/cli-generated-rule-delivery/spec.md

File renamed without changes.

openspec/changes/runtime-entitlement-withheld/specs/cli-rule-reconciliation/spec.md renamed to openspec/changes/archive/2026-09-27-runtime-entitlement-withheld/specs/cli-rule-reconciliation/spec.md

File renamed without changes.

openspec/changes/runtime-entitlement-withheld/tasks.md renamed to openspec/changes/archive/2026-09-27-runtime-entitlement-withheld/tasks.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,14 @@
5959
- [x] 5.2 Changeset (`patch`, pre-1.0) stating the exit-code change and what a
6060
CI owner does about it.
6161
- [x] 5.3 `pnpm typecheck` and `pnpm lint`.
62-
- [ ] 5.4 Archive the change on this PR.
62+
- [x] 5.4 Archive the change on this PR.
6363

64-
## 6. Follow-up, after taskless/taskless#207 deploys
64+
## 6. Follow-up (tracked outside this change)
6565

66-
- [ ] 6.1 Regenerate `api.schema.json` / `api.d.ts` and replace the defensive
67-
reads in 2.3 and 4.2 with the typed field. May land as its own PR.
68-
Do not regenerate before the cloud restores `signature` on the
69-
config-based (static) file sets: the live schema drops it today as an
70-
interim state, and a refresh now would vendor that gap.
66+
- [x] 6.1 Ship the entitlement as a field that MIGHT be present
67+
(`MayCarryEntitlement<T>`), so this change does not wait on
68+
taskless/taskless#207. Regenerating the schema and tightening the types
69+
once the service always returns `entitlement` and static file-set
70+
signatures is its own feature: #409.
71+
- [x] 6.2 Gating `--dangerously-run-scripts` on a TTY or `CI=1`, so an agent
72+
cannot add it to get green: #408.

‎openspec/specs/cli-check/spec.md‎

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ When the `--json` flag is set, the CLI SHALL output each `CheckResult` as a JSON
113113

114114
### Requirement: Check subcommand exit codes reflect error severity
115115

116-
The CLI SHALL exit with code 0 when no error-severity matches are found (including when only warnings, info, or hints exist). The CLI SHALL exit with code 1 when at least one error-severity match is found.
116+
The CLI SHALL exit with code 0 when no error-severity matches are found (including when only warnings, info, or hints exist) and no runtime rule was withheld for entitlement. The CLI SHALL exit with code 1 when at least one error-severity match is found. The CLI SHALL also exit with code 1 when reconciliation completed and the response's `entitlement.withheld` is non-empty, whatever the findings, in both human and `--json` modes.
117117

118118
#### Scenario: Exit 0 when clean
119119

@@ -130,6 +130,17 @@ The CLI SHALL exit with code 0 when no error-severity matches are found (includi
130130
- **WHEN** the scanner produces at least one result with severity "error"
131131
- **THEN** the process SHALL exit with code 1
132132

133+
#### Scenario: Exit 1 when a runtime rule is withheld for entitlement
134+
135+
- **WHEN** reconciliation returns a non-empty `entitlement.withheld` and the scan produces zero results
136+
- **THEN** the process SHALL exit with code 1
137+
- **AND** under `--json`, `success` SHALL be `false`
138+
139+
#### Scenario: Entitlement without a withheld file does not fail
140+
141+
- **WHEN** reconciliation returns `entitlement.runtimeSignatures: false` with an empty or absent `withheld`, and the scan produces zero results
142+
- **THEN** the process SHALL exit with code 0
143+
133144
### Requirement: Check subcommand respects global working directory flag
134145

135146
The `check` subcommand SHALL use the resolved working directory from the global `-d` flag (or `process.cwd()` if not specified) as the target directory for `.taskless/` validation and scanner execution.
@@ -493,3 +504,35 @@ A notice SHALL NOT affect the exit code.
493504
- **WHEN** a `check` run produces no notices
494505
- **THEN** human output SHALL print no `Notice: ` line
495506
- **AND** `--json` SHALL omit the `notices` field
507+
508+
### Requirement: Check reports runtime rules withheld for entitlement as a plan outcome
509+
510+
When reconciliation completes and returns a non-empty `entitlement.withheld`, `taskless check` SHALL NOT execute any withheld rule, SHALL report each local runtime rule whose reported `check.ts` path appears in `withheld` as skipped with a reason stating that runtime rules are not included in the organization's plan, and SHALL NOT describe it as unsafe, unknown, drifted, or tampered. The human output SHALL include one notice naming the withheld rules, the `entitlement.reason`, and the `entitlement.upgradeUrl` when present. Under `--json`, the output SHALL carry an additive, optional `entitlement` object with `runtimeSignatures`, `reason`, `upgradeUrl`, and `withheld` (the local rule names), present only when reconciliation returned `runtimeSignatures: false`. This is a verified outcome and SHALL NOT be treated as one of the unverified paths that leave the exit code unchanged.
511+
512+
#### Scenario: Withheld rule is named with its cause
513+
514+
- **WHEN** an authenticated `check` reconciles and `entitlement.withheld` lists the reported `check.ts` of runtime rule `no-env-leak`
515+
- **THEN** `no-env-leak` SHALL NOT execute
516+
- **AND** its skip reason SHALL state that runtime rules are not included in the plan
517+
- **AND** its skip reason SHALL NOT mention unsafe, unknown, or drift
518+
519+
#### Scenario: Upgrade URL is shown once
520+
521+
- **WHEN** two runtime rules are withheld and `entitlement.upgradeUrl` is present
522+
- **THEN** the human output SHALL print the upgrade URL exactly once, in one notice naming both rules and the reason
523+
524+
#### Scenario: Entitlement appears under --json
525+
526+
- **WHEN** a runtime rule is withheld and `--json` is set
527+
- **THEN** stdout SHALL include `entitlement` with `runtimeSignatures: false`, `reason`, `upgradeUrl`, and `withheld` naming the rule
528+
- **AND** `skipped` SHALL still list the rule with its plan reason
529+
530+
#### Scenario: A server without the entitlement object is unchanged
531+
532+
- **WHEN** reconciliation returns the four arrays and no `entitlement` object
533+
- **THEN** `check` SHALL behave exactly as before this change, including its exit code and skip reasons
534+
535+
#### Scenario: Degrade paths still never fail
536+
537+
- **WHEN** `check` runs logged out, with `--anonymous`, or reconciliation cannot complete
538+
- **THEN** the exit code SHALL NOT change because runtime rules were skipped, as before this change

‎openspec/specs/cli-generated-rule-delivery/spec.md‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,30 @@ whose destination the client computed itself.
4646
- **WHEN** a delivered file declares a path containing `..` or an absolute path
4747
- **THEN** the CLI SHALL refuse the entire rule
4848
- **AND** SHALL NOT have created any file or directory for it
49+
50+
### Requirement: A runtime rule written under a plan without runtime signatures says it will not run
51+
52+
When the CLI writes a runtime rule from a response whose `entitlement.runtimeSignatures` is exactly `false` (from `rule create`, `rule improve`, or a restore during `check`), it SHALL still write the rule, and SHALL emit a warning that the rule is on disk but will not run on the organization's current plan, including the `upgradeUrl` when it is an absolute `https:` URL. The warning SHALL be carried in the command's existing notices, so it appears in human output and under `--json`. A restore under such a response SHALL NOT state or imply that the next `check` will bless or run the rule. A response with no `entitlement` object, or with `runtimeSignatures: true`, SHALL produce no such warning.
53+
54+
#### Scenario: Created runtime rule under an unentitled plan
55+
56+
- **WHEN** `rule create` receives a generated runtime rule with `entitlement.runtimeSignatures: false`
57+
- **THEN** the CLI SHALL write the rule
58+
- **AND** SHALL warn that it will not run on the current plan
59+
- **AND** under `--json` the warning SHALL appear in `notices`
60+
61+
#### Scenario: Restore under an unentitled plan does not promise blessing
62+
63+
- **WHEN** a restore during `check` writes a runtime rule and the restore response carries `entitlement.runtimeSignatures: false`
64+
- **THEN** the restore notice SHALL say the rule's bytes were restored but will not run on the current plan
65+
- **AND** SHALL NOT say the next `check` blesses it
66+
67+
#### Scenario: Entitled or legacy responses do not warn
68+
69+
- **WHEN** a runtime rule is written from a response with no `entitlement` object or with `runtimeSignatures: true`
70+
- **THEN** the CLI SHALL emit no entitlement warning
71+
72+
#### Scenario: Static rules never warn
73+
74+
- **WHEN** `rule create` writes an `sg` or `vale` rule
75+
- **THEN** the CLI SHALL emit no entitlement warning

‎openspec/specs/cli-rule-reconciliation/spec.md‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,3 +278,34 @@ upgrading is regeneration and SHALL remain an explicit action.
278278

279279
- **WHEN** a newer generation of the same rule exists server-side
280280
- **THEN** re-fetch SHALL still return the bytes matching the signature the client reported
281+
282+
### Requirement: The CLI reads the reconcile entitlement object
283+
284+
The CLI SHALL read the optional `entitlement` object on a successful reconcile response. It SHALL treat the organization as unentitled only when `entitlement.runtimeSignatures` is exactly `false`; an absent, malformed, or `true` value SHALL be treated as no entitlement outcome, leaving the four buckets to drive execution exactly as before. For an unentitled response the CLI SHALL read `reason`, `upgradeUrl`, and `withheld` (a list of `{ ruleId, file }`), SHALL drop a `withheld` entry without a string `file`, and SHALL surface `upgradeUrl` only when it is an absolute `https:` URL.
285+
286+
`entitlement.withheld` SHALL be a disposition distinct from the four buckets: a withheld file SHALL NOT be executed, SHALL NOT be surfaced as tamper, drift, or never-issued, and SHALL NOT be sent to restore. The CLI SHALL match a withheld entry to a local runtime rule by the `file` it reported, since the entry carries no signature. A withheld entry that matches no reported file SHALL still count as withheld for the purpose of the exit code.
287+
288+
#### Scenario: Absent entitlement changes nothing
289+
290+
- **WHEN** a reconcile response carries no `entitlement` object
291+
- **THEN** the CLI SHALL drive execution from `run`, `unsafe`, `unknown`, and `missing` exactly as before
292+
293+
#### Scenario: Entitled response changes nothing
294+
295+
- **WHEN** a reconcile response carries `entitlement: { runtimeSignatures: true }`
296+
- **THEN** the CLI SHALL drive execution from the four buckets exactly as before
297+
298+
#### Scenario: Withheld is matched by reported path
299+
300+
- **WHEN** `entitlement.withheld` lists `{ ruleId, file }` and `file` equals the path the CLI reported for a runtime rule's `check.ts`
301+
- **THEN** the CLI SHALL classify that rule as withheld for entitlement and SHALL NOT execute it
302+
303+
#### Scenario: Withheld is not repaired
304+
305+
- **WHEN** a runtime rule is withheld for entitlement
306+
- **THEN** the CLI SHALL NOT request a restore for it
307+
308+
#### Scenario: A malformed upgrade URL is not shown
309+
310+
- **WHEN** `entitlement.upgradeUrl` is not an absolute `https:` URL
311+
- **THEN** the CLI SHALL omit it from human and `--json` output

0 commit comments

Comments
 (0)