instar 1.3.963 → 1.3.965
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +32 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/UnjustifiedStopGate.d.ts +14 -0
- package/dist/core/UnjustifiedStopGate.d.ts.map +1 -1
- package/dist/core/UnjustifiedStopGate.js +52 -2
- package/dist/core/UnjustifiedStopGate.js.map +1 -1
- package/dist/data/provenanceCoverage.d.ts +2 -0
- package/dist/data/provenanceCoverage.d.ts.map +1 -1
- package/dist/data/provenanceCoverage.js +29 -4
- package/dist/data/provenanceCoverage.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +5 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +19 -19
- package/src/data/provenanceCoverage.ts +30 -3
- package/src/scaffold/templates.ts +5 -0
- package/upgrades/1.3.964.md +123 -0
- package/upgrades/1.3.965.md +58 -0
- package/upgrades/side-effects/enrol-unjustified-stop-gate.md +126 -0
- package/upgrades/side-effects/reminder-agent-awareness.md +124 -0
- package/upgrades/side-effects/spec-round10-manifest-rows.md +13 -0
|
@@ -237,6 +237,9 @@ export const DP_COMPLETION_CLAIM_VERIFY = 'completion-claim-verify';
|
|
|
237
237
|
/** Feedback cluster evidence → owned-work readiness judgment. */
|
|
238
238
|
export const DP_FEEDBACK_READINESS = 'feedback-readiness';
|
|
239
239
|
|
|
240
|
+
/** The stop-justified authority (src/core/UnjustifiedStopGate.ts). */
|
|
241
|
+
export const DP_UNJUSTIFIED_STOP_GATE = 'unjustified-stop-gate';
|
|
242
|
+
|
|
240
243
|
// ───────────────────────────────────────────────────────────────────────────
|
|
241
244
|
// The census
|
|
242
245
|
// ───────────────────────────────────────────────────────────────────────────
|
|
@@ -462,12 +465,36 @@ export const PROVENANCE_COVERAGE: ReadonlyArray<ProvenanceCoverageEntry> = [
|
|
|
462
465
|
'Should-I-reply verdict over an inbound message; enrollment queued in the ACT-1193 uniform-provenance retrofit backlog.',
|
|
463
466
|
},
|
|
464
467
|
{
|
|
465
|
-
decisionPoint:
|
|
468
|
+
decisionPoint: DP_UNJUSTIFIED_STOP_GATE,
|
|
466
469
|
component: 'UnjustifiedStopGate',
|
|
467
|
-
status: '
|
|
470
|
+
status: 'wired',
|
|
471
|
+
// The highest-volume UNENROLLED decision point in the census: ~1343 calls
|
|
472
|
+
// in the last 7 days (GET /metrics/features), second only to the tone gate
|
|
473
|
+
// among gates. A per-UTC-day COUNT budget rather than `full`: this fires on
|
|
474
|
+
// every stop attempt across every session, and an always-on gate must not
|
|
475
|
+
// grow the provenance archive without a hard ceiling. The ~250-byte
|
|
476
|
+
// decision_quality row is written for every settlement regardless, so the
|
|
477
|
+
// COUNTS stay complete even when the archive is valved.
|
|
478
|
+
volumeClass: 'budget:300',
|
|
479
|
+
// Content-bearing: the gate judges a session's stop rationale. It enters the
|
|
480
|
+
// row as IDENTITY ONLY — hashes, bounds, and code-derived features — never
|
|
481
|
+
// the rationale text or any transcript slice.
|
|
468
482
|
contentClass: 'content-bearing',
|
|
483
|
+
// ── Grading posture ──────────────────────────────────────────────────
|
|
484
|
+
// MEASUREMENT-ONLY, declared rather than left as a silent gap.
|
|
485
|
+
gradingPosture: 'measurement-only',
|
|
486
|
+
gradingReason:
|
|
487
|
+
'No honest outcome rule exists YET. Grading "was this stop justified?" needs a ' +
|
|
488
|
+
'downstream fact — did the run resume and do real work after the gate allowed a ' +
|
|
489
|
+
'stop, or did the agent genuinely have nothing left after the gate held one? Those ' +
|
|
490
|
+
'signals exist (the resume queue, the autonomous liveness reconciler) but joining ' +
|
|
491
|
+
'them to a decision row is real plumbing, not a predicate. Enrolling the DECISION ' +
|
|
492
|
+
'side first is deliberate and is what this posture is for: the rows accumulate now, ' +
|
|
493
|
+
'so when the outcome join lands there is history to grade instead of a cold start. ' +
|
|
494
|
+
'Recording without grading is stated here so the census shows it rather than ' +
|
|
495
|
+
'implying this point is measured when only half of it is.',
|
|
469
496
|
reason:
|
|
470
|
-
'
|
|
497
|
+
'The stop-justified authority — the highest-volume unenrolled point in the census.',
|
|
471
498
|
},
|
|
472
499
|
{
|
|
473
500
|
decisionPoint: 'coherence-review',
|
|
@@ -657,6 +657,11 @@ Two layers keep my machine-to-machine \"ropes\" (Tailscale / LAN / Cloudflare) h
|
|
|
657
657
|
- Mark delivered when done: \`curl -X POST -H "Authorization: Bearer $AUTH" http://localhost:${port}/commitments/:id/deliver\`
|
|
658
658
|
- The PromiseBeacon fires cadenced heartbeats on open commitments so you actually follow through (and surfaces atRisk items), and the commitment-check job surfaces overdue ones.
|
|
659
659
|
- **A promise survives its owning session's death (Escalation, ships dark).** When a beacon-enabled commitment's session dies before delivering, the beacon no longer silently terminalizes it — it escalates: **Rung 1** revives a fresh session bound to the topic to follow through; **Rung 2** sends an honest, state-specific interim status ("my session ended; I'm picking it back up") when it can't revive; **Rung 3** is a bounded loud give-up to the operator's Attention queue. A revived session carries **revivalMode** — every side-effecting external operation is BLOCKED until it revalidates (\`POST /commitments/:id/revalidate\` with a restated current-intent summary), so a revive grants no new authority. \`GET /commitments/:id\` exposes the escalation fields (\`escalationAttempts\`, \`currentRung\`, \`revalidatedAt\`); \`GET /commitments/escalation-metrics\` is the operator-facing aggregate counters. Ships dark + dry-run-first (\`monitoring.promiseBeacon.escalation\`).
|
|
660
|
+
- **A promise with a DATE produces a real reminder (ACT-724 step 1, ships dark).** When you say "I'll check in on this by Friday", set \`checkInAt\` (an absolute ISO instant — resolve "Friday" to a real moment at creation time; never store a bare date) on the commitment. A recurring reconciler then posts EXACTLY ONE fixed-template reminder into that commitment's own topic when the instant arrives, and stops. Delivering or withdrawing the commitment first means no reminder ever fires — teardown is a status check, nothing to cancel.
|
|
661
|
+
- Read the dated backlog: \`curl -H "Authorization: Bearer $AUTH" http://localhost:${port}/commitments/check-in-reminder\` → \`{ enabled, dryRun, datedCount, pending, undelivered }\`. **\`undelivered\` is the one to look at**: those are promises the user did NOT receive after retries were exhausted.
|
|
662
|
+
- Run one pass now: \`curl -X POST -H "Authorization: Bearer $AUTH" http://localhost:${port}/commitments/check-in-reminder/pass\` (idempotent — a re-run sends nothing).
|
|
663
|
+
- The guarantee is **at-least-once, deduped at the delivery layer** — not exactly-once. A reminder is marked sent ONLY after it actually sent, so a failed send retries (bounded) instead of being recorded as delivered; the crash-window duplicate is absorbed by the relay's content dedup.
|
|
664
|
+
- Ships dark (\`commitments.checkInReminder\`, dryRun defaulting TRUE, job \`commitment-checkin-reminder\` enabled:false). **Honest scope:** while the reconciler runs, no dated commitment can slip past it — but nothing yet guarantees the reconciler runs, so "structurally impossible to have a dated promise without a reminder" is NOT true yet. The creation-time gate that closes it is step 2.
|
|
660
665
|
- **When to use** (PROACTIVE — this is the trigger): the moment you promise the user a future action, open a commitment. NEVER improvise the follow-through with a raw \`sleep\`/background timer or by "remembering" — those do not survive a session ending, a restart, or compaction, so the promise is silently dropped. A registered commitment is the ONLY durable path. (This is distinct from the Evolution Action Queue / \`/commit-action\`, which tracks self-improvement items, not promises to the user.)
|
|
661
666
|
- **Action-Claim Follow-Through Sentinel (signal-only, dark by default).** A backstop for the word≠action gap (you say "relaunching now" / "I'll push the change" and then don't). A thin Stop hook posts each finished conversational turn to \`POST /action-claim/observe\`, which classifies a CONCRETE future-action claim (restart/relaunch/push/merge/deploy/fix/…) and opens an idempotent follow-through commitment for the topic — so the existing PromiseBeacon + the revival path make sure it actually happens. High-precision (vague "I'll take a look" never triggers it), de-duplicated by \`externalKey\` (a restated claim updates one commitment, not many), auto-expiring, per-topic capped. It NEVER blocks a message. Off by default; enable with the top-level \`actionClaim.enabled\` (dev-first soak before fleet — the block is top-level, NOT nested under \`messaging\`, which is an array of adapters). It now covers **Slack** conversations too (a promise born in a Slack thread registers a durable commitment bound to the conversation's minted id, delivered back into that exact thread across restarts — dev-gated dark behind \`messaging.actionClaim.slack\`, dryRun-first) and **time-boxed conversational promises** ("I'll post that in about 5 minutes / by EOD / I'll check in"), not just dev-ops verbs. Proactive: user asks "why did a commitment appear when I said I'd restart something / promised to post in 5 min?" → that's this sentinel tracking your stated action so it isn't silently dropped.
|
|
662
667
|
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
`UnjustifiedStopGate` — the authority that decides whether an agent may stop
|
|
9
|
+
working — is now enrolled in the LLM-Decision Quality Meter.
|
|
10
|
+
|
|
11
|
+
At ~1343 calls / 7 days it was the **highest-volume unenrolled decision point in
|
|
12
|
+
the census** and the second-busiest gate overall, yet nothing recorded what it
|
|
13
|
+
decided or on what basis. The meter can only answer "is this judgment any good?"
|
|
14
|
+
for points that record; this one didn't, so the question was unanswerable in
|
|
15
|
+
principle.
|
|
16
|
+
|
|
17
|
+
**What the row carries — identity only.** The gate reads untrusted session
|
|
18
|
+
content (the stop rationale and up to ten recent conversation turns). None of it
|
|
19
|
+
is recorded. The context is an explicit allowlist of derived values:
|
|
20
|
+
|
|
21
|
+
- `stopReasonSha256` + `stopReasonChars` — distinguishes two rationales without
|
|
22
|
+
reproducing either
|
|
23
|
+
- `artifactCount` / `artifactKinds` — the evidence the authority could cite
|
|
24
|
+
- `signals` — code-derived booleans (not user text)
|
|
25
|
+
- `recentTurnCount` / `recentTurnChars` — conversation shape only
|
|
26
|
+
|
|
27
|
+
An allowlist rather than a filtered copy, so a future input field cannot appear
|
|
28
|
+
in the row by default. Tests plant a fake API key and password in the input and
|
|
29
|
+
assert neither reaches the serialized context.
|
|
30
|
+
|
|
31
|
+
**Declared `measurement-only`, on purpose.** Recording a decision is not grading
|
|
32
|
+
it. Grading this point needs a downstream fact — did work resume after an allowed
|
|
33
|
+
stop; did the agent genuinely continue after a refused one — and joining those
|
|
34
|
+
signals to a decision row is real plumbing. Rather than let `wired` imply more
|
|
35
|
+
than exists, the entry declares `gradingPosture: 'measurement-only'` with an
|
|
36
|
+
argued reason, so `findWiredWithoutGraders` reports it as an explicit posture
|
|
37
|
+
instead of a silent contradiction.
|
|
38
|
+
|
|
39
|
+
That distinction is the point: the easy version of this change moves a census
|
|
40
|
+
number and leaves a reader believing the busiest judgment call is being
|
|
41
|
+
evaluated. It isn't yet. Rows accumulate now so the grader has history when it
|
|
42
|
+
lands, rather than starting cold.
|
|
43
|
+
|
|
44
|
+
Volume is valved at `budget:300`/day (an always-on gate must not grow the archive
|
|
45
|
+
unbounded); the `decision_quality` row is written for every settlement regardless,
|
|
46
|
+
so counts stay complete.
|
|
47
|
+
|
|
48
|
+
Spec clarification for the (unshipped, dark) cross-machine capability registry: `scanState` and row
|
|
49
|
+
presence are INDEPENDENT. A machine with a model manifest and no doorway scan-state now returns
|
|
50
|
+
`scanState: "never-observed"` **together with** its manifest-derived rows at status `unknown`,
|
|
51
|
+
instead of zero rows.
|
|
52
|
+
|
|
53
|
+
The earlier wording ("`200` with `scanState: never-observed` and empty entries when no observation
|
|
54
|
+
exists") was ambiguous, and the ambiguity produced a real defect: the Increment-2 route derived ten
|
|
55
|
+
rows from the real manifest and served none of them, because "no observation" was read as "no scan".
|
|
56
|
+
Manifest rows ARE observations — the record admits them with `sourceDetail: doorway-manifest`, and
|
|
57
|
+
the status matrix gives `manifest-only` evidence the status `unknown` precisely so documented
|
|
58
|
+
existence is reportable without ever implying reachability.
|
|
59
|
+
|
|
60
|
+
The rule now carries its failing input: on a machine with a manifest and no scan-state the route
|
|
61
|
+
MUST return rows with status `unknown`; a change that makes it return zero rows must fail a test.
|
|
62
|
+
|
|
63
|
+
## What to Tell Your User
|
|
64
|
+
|
|
65
|
+
Nothing changes in how the agent behaves. This is instrumentation on an internal
|
|
66
|
+
judgment — no new messages, no new settings, nothing you interact with.
|
|
67
|
+
|
|
68
|
+
What it buys: the check that decides whether the agent may stop working is the
|
|
69
|
+
busiest judgment call in the system, and until now nothing recorded what it
|
|
70
|
+
decided. Now it does. So the question "is that check actually any good — too
|
|
71
|
+
strict, too lenient, wrong half the time?" stops being unanswerable and becomes
|
|
72
|
+
answerable once enough history accumulates.
|
|
73
|
+
|
|
74
|
+
Worth being precise about what is NOT true yet: the decisions are recorded, not
|
|
75
|
+
graded. Knowing whether a given call was *right* needs a later fact (did work
|
|
76
|
+
resume after a stop was allowed?) that isn't connected up yet. The record says so
|
|
77
|
+
explicitly rather than implying the check is being evaluated when only half of
|
|
78
|
+
that is in place.
|
|
79
|
+
|
|
80
|
+
None of the conversation is stored — only a fingerprint of the stop reason and
|
|
81
|
+
counts describing the decision's shape.
|
|
82
|
+
|
|
83
|
+
Nothing. This is a design-document correction for a feature that is switched off everywhere, so
|
|
84
|
+
there is no user-visible change to announce. If a user asks why a capability list looked empty on a
|
|
85
|
+
machine that had never run a scan, the honest answer is that it was a specification ambiguity: the
|
|
86
|
+
catalog rows existed and were being discarded, and the corrected rule serves them marked as
|
|
87
|
+
unverified rather than hiding them.
|
|
88
|
+
|
|
89
|
+
## Summary of New Capabilities
|
|
90
|
+
|
|
91
|
+
- The stop-justification authority now records every decision it makes, so its
|
|
92
|
+
quality can be evaluated rather than assumed.
|
|
93
|
+
- Recording is identity-only by construction: an explicit allowlist of derived
|
|
94
|
+
values, tested against planted secrets, so session content cannot leak into the
|
|
95
|
+
provenance store.
|
|
96
|
+
- The census now distinguishes "recorded and gradeable" from "recorded but not
|
|
97
|
+
yet gradeable", so the coverage number reflects what actually exists.
|
|
98
|
+
|
|
99
|
+
None. This amendment adds no capability — it removes an ambiguity that was silently costing one
|
|
100
|
+
(the ability to see what a machine's catalog claims before anything has probed it).
|
|
101
|
+
|
|
102
|
+
## Evidence
|
|
103
|
+
|
|
104
|
+
- `tests/unit/unjustified-stop-gate-provenance-enrollment.test.ts` (11): the
|
|
105
|
+
census declares it wired with a volume valve and content class; the
|
|
106
|
+
measurement-only posture carries a real argument; the provenance block names
|
|
107
|
+
the typed constant with the prompt hash; **planted secrets never reach the
|
|
108
|
+
context**; the hash distinguishes different rationales (so it isn't a constant);
|
|
109
|
+
empty input doesn't throw; the verdict is unchanged and `attribution` survives
|
|
110
|
+
alongside the new block.
|
|
111
|
+
- Census ratchet: pending baseline shrinks by exactly one; typed-registration
|
|
112
|
+
check independently verifies the source imports `DP_UNJUSTIFIED_STOP_GATE`.
|
|
113
|
+
- 72 green across the affected suites.
|
|
114
|
+
|
|
115
|
+
Found by running the Increment-2 route's local-projection path against a real agent state dir rather
|
|
116
|
+
than reading the diff: `durable projection ABSENT / fallback readDoorwaySources entries = 10 /
|
|
117
|
+
scanState = never-observed`, and then observing that `classifyProjection` early-returned a single
|
|
118
|
+
`no-data-yet` row, discarding all ten.
|
|
119
|
+
|
|
120
|
+
## Audience
|
|
121
|
+
|
|
122
|
+
Agent-only, maturity experimental. No user-visible behavior: the feature is dark everywhere, no
|
|
123
|
+
route ships enabled by this change, and the amendment is spec text only.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
The dated check-in reminder (shipped in #1630) is now in the agent CLAUDE.md
|
|
9
|
+
template — and reaches EXISTING agents, not just fresh installs.
|
|
10
|
+
|
|
11
|
+
#1630 shipped routes, a built-in job, and 55 tests, and nothing in the agent
|
|
12
|
+
template. Per the Agent Awareness Standard, "an agent that doesn't know about a
|
|
13
|
+
capability effectively doesn't have it." No agent would have set `checkInAt`, so
|
|
14
|
+
the reconciler would have scanned an empty set every five minutes, found nothing,
|
|
15
|
+
and reported success forever. Working code, passing tests, green health check,
|
|
16
|
+
zero reminders.
|
|
17
|
+
|
|
18
|
+
**The migration is the substantive half.** The existing Commitments arm is
|
|
19
|
+
guarded by `if (!content.includes('**Commitments & Follow-Through**'))` — every
|
|
20
|
+
running agent already has that section, so anything added inside that branch
|
|
21
|
+
reaches fresh installs ONLY. That is the exact Migration Parity failure the
|
|
22
|
+
standard names, and it looks correct in a diff. The new subsection therefore gets
|
|
23
|
+
its own arm keyed on "section present AND subsection absent", with a test
|
|
24
|
+
asserting that separation rather than trusting it.
|
|
25
|
+
|
|
26
|
+
A drifted CLAUDE.md gets an append rather than a skip: a misplaced paragraph
|
|
27
|
+
beats an agent permanently unaware of a capability because it customised its own
|
|
28
|
+
notes.
|
|
29
|
+
|
|
30
|
+
The text states honest scope — dark by default, at-least-once rather than
|
|
31
|
+
exactly-once, and that "structurally impossible to have a dated promise without a
|
|
32
|
+
reminder" is NOT true yet. A briefing that oversells produces an agent
|
|
33
|
+
confidently relying on something that isn't running.
|
|
34
|
+
|
|
35
|
+
## What to Tell Your User
|
|
36
|
+
|
|
37
|
+
Nothing changes in behaviour. This is the difference between the reminder
|
|
38
|
+
feature existing and it being usable — without it, no agent would ever know to
|
|
39
|
+
attach a date to a promise, and the feature would have run over an empty list
|
|
40
|
+
indefinitely while looking healthy.
|
|
41
|
+
|
|
42
|
+
## Summary of New Capabilities
|
|
43
|
+
|
|
44
|
+
- Agents now know dated commitments produce reminders, which field to set, and
|
|
45
|
+
which field reveals promises that were never delivered.
|
|
46
|
+
- Existing agents receive it on update, not only new installs.
|
|
47
|
+
- The briefing states what the feature does NOT yet guarantee, so an agent
|
|
48
|
+
cannot over-rely on a capability that ships switched off.
|
|
49
|
+
|
|
50
|
+
## Evidence
|
|
51
|
+
|
|
52
|
+
- `tests/unit/claudemd-check-in-reminder-awareness.test.ts` (10): the template
|
|
53
|
+
documents the endpoint, names the `checkInAt` field an agent must set, carries
|
|
54
|
+
a proactive trigger rather than a bare endpoint, states the honest scope, and
|
|
55
|
+
points at `undelivered`; the migrator arm exists, is SEPARATE from the
|
|
56
|
+
Commitments arm, is idempotent, records what it did, and appends rather than
|
|
57
|
+
skipping on drift.
|
|
58
|
+
- `feature-delivery-completeness`: 126 tests green.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Side-effects review — enrol-unjustified-stop-gate
|
|
2
|
+
|
|
3
|
+
**Change:** enrol `UnjustifiedStopGate` into the LLM-Decision Quality Meter
|
|
4
|
+
(`pending:backlog:decision-quality-enrolment` → `wired`), with an identity-only
|
|
5
|
+
provenance context and an explicitly declared `measurement-only` grading posture.
|
|
6
|
+
|
|
7
|
+
**Why this point:** at ~1343 calls / 7 days (`GET /metrics/features`) it is the
|
|
8
|
+
highest-volume UNENROLLED decision point in the census, and the second-busiest
|
|
9
|
+
gate overall. This is the second half of the run's census task — the half where
|
|
10
|
+
the backlog actually shrinks rather than merely becoming legible.
|
|
11
|
+
|
|
12
|
+
**Tier:** 1 — one census entry, one context builder, one provenance block. No new
|
|
13
|
+
route, config key, flag, or durable store.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 1. Over-block — what legitimate inputs does this reject that it shouldn't?
|
|
18
|
+
|
|
19
|
+
**Nothing.** Enrollment adds an `options.provenance` block to an existing
|
|
20
|
+
provider call. It introduces no predicate, no branch, and no rejection path. The
|
|
21
|
+
gate's continue/allow/escalate verdict is computed exactly as before — asserted
|
|
22
|
+
directly by a test rather than argued.
|
|
23
|
+
|
|
24
|
+
The one build-time narrowing: the census ratchet now requires this point to stay
|
|
25
|
+
declared. Removing the enrollment without updating the census fails CI, which is
|
|
26
|
+
the intent.
|
|
27
|
+
|
|
28
|
+
## 2. Under-block — what failure modes does this still miss?
|
|
29
|
+
|
|
30
|
+
- **It records decisions; it does not grade them.** This is the honest half and
|
|
31
|
+
is declared as `gradingPosture: 'measurement-only'` with a ≥40-char argument,
|
|
32
|
+
so `findWiredWithoutGraders` reports it as an explicit posture rather than a
|
|
33
|
+
silent contradiction. **The census number improves and the point is still not
|
|
34
|
+
measured end-to-end** — anyone reading "wired" without reading the posture will
|
|
35
|
+
overestimate what exists. That is why the posture is declared, tested, and
|
|
36
|
+
stated in the release note.
|
|
37
|
+
- **Volume is capped at `budget:300`/day.** A day with more than 300 stop
|
|
38
|
+
decisions archives the first 300; the `decision_quality` row is still written
|
|
39
|
+
for every settlement, so counts stay complete while the archive is valved. A
|
|
40
|
+
loud `droppedByBudget` counter surfaces the truncation.
|
|
41
|
+
- **The context is a fixed projection.** If a future reader needs a field I did
|
|
42
|
+
not include, the history will not have it retroactively. I chose the minimum
|
|
43
|
+
that reconstructs decision shape; widening later is additive but not backfillable.
|
|
44
|
+
|
|
45
|
+
## 3. Level-of-abstraction fit
|
|
46
|
+
|
|
47
|
+
The context builder is a private method on the gate, because only the gate knows
|
|
48
|
+
which of its inputs are trusted evidence and which are untrusted session text.
|
|
49
|
+
Building it anywhere else would require exporting that distinction — and a
|
|
50
|
+
downstream builder that guesses wrong is precisely how a transcript ends up in
|
|
51
|
+
a provenance store.
|
|
52
|
+
|
|
53
|
+
The provenance block rides the existing `options` argument alongside
|
|
54
|
+
`attribution`, so enrollment adds a signal rather than displacing one (tested).
|
|
55
|
+
|
|
56
|
+
## 4. Signal vs authority compliance
|
|
57
|
+
|
|
58
|
+
`docs/signal-vs-authority.md`. Enrollment holds **no authority at all** — it is
|
|
59
|
+
pure observability. The settlement seam consumes the block and records on its own
|
|
60
|
+
path; it never reaches the model and never alters the verdict.
|
|
61
|
+
|
|
62
|
+
The gate itself remains what it was: an LLM authority with a deterministic
|
|
63
|
+
fail-open contract. This change deliberately does not touch that.
|
|
64
|
+
|
|
65
|
+
**The content-bearing contract is the safety property here**, and it is enforced
|
|
66
|
+
by test rather than by care: the fixtures plant a fake API key and a fake
|
|
67
|
+
password in the untrusted input, and the tests assert neither appears anywhere in
|
|
68
|
+
the serialized context. A hash-distinctness test prevents a constant or empty
|
|
69
|
+
hash from making the identity field useless while still passing the leak tests.
|
|
70
|
+
|
|
71
|
+
## 5. Interactions
|
|
72
|
+
|
|
73
|
+
- **`/metrics/features`** — unchanged; `attribution.component` is preserved
|
|
74
|
+
(explicitly tested, because losing it would silently drop this component from
|
|
75
|
+
the cost surface).
|
|
76
|
+
- **The census ratchet** — the pending baseline shrinks by exactly one line, which
|
|
77
|
+
is the direction it is allowed to move. The typed-registration check
|
|
78
|
+
independently verifies the enrolling source imports `DP_UNJUSTIFIED_STOP_GATE`
|
|
79
|
+
rather than restating the string.
|
|
80
|
+
- **The breaker / fail-open path** — untouched. A provenance write failure is
|
|
81
|
+
contained by the recorder's own fail-open contract, so it cannot convert into a
|
|
82
|
+
stop-decision failure.
|
|
83
|
+
- **The prompt hash** — reused as `promptId`, so a row records WHICH prompt
|
|
84
|
+
decided. The gate already computed it; nothing new is derived.
|
|
85
|
+
|
|
86
|
+
## 6. External surfaces
|
|
87
|
+
|
|
88
|
+
- No route, config key, flag, env var, CLI, message, or notification.
|
|
89
|
+
- `GET /decision-quality` will show one more `wired` point and one fewer
|
|
90
|
+
`pending`. Field shapes unchanged.
|
|
91
|
+
- No user-visible behaviour whatsoever.
|
|
92
|
+
|
|
93
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
94
|
+
|
|
95
|
+
**Unified.** The census is a shipped source constant, byte-identical on every
|
|
96
|
+
install, so every machine enrolls the same point with the same volume valve and
|
|
97
|
+
content class. The provenance rows themselves inherit the meter's existing
|
|
98
|
+
posture — this change adds no new state surface, no notice, and no generated URL.
|
|
99
|
+
|
|
100
|
+
## 8. Rollback cost
|
|
101
|
+
|
|
102
|
+
Revert. The census entry returns to `pending:` (restoring the baseline line), the
|
|
103
|
+
provenance block disappears, and previously-recorded rows remain inertly in the
|
|
104
|
+
store — harmless, and still readable if the enrollment returns.
|
|
105
|
+
|
|
106
|
+
## Second-pass review
|
|
107
|
+
|
|
108
|
+
**Not required** by the Phase-5 trigger list: enrollment makes no block/allow
|
|
109
|
+
decision, touches no session lifecycle, and adds no gate. The gate it observes is
|
|
110
|
+
unchanged.
|
|
111
|
+
|
|
112
|
+
Self-review, recorded rather than skipped:
|
|
113
|
+
|
|
114
|
+
1. **Is this the honest version or the number-improving version?** The
|
|
115
|
+
temptation was real: flipping `pending` → `wired` shrinks census debt whether
|
|
116
|
+
or not anything can be graded. I took the declared `measurement-only` path
|
|
117
|
+
because the census distinguishes it and `findWiredWithoutGraders` surfaces it
|
|
118
|
+
— the gap stays visible instead of being absorbed into a better-looking
|
|
119
|
+
number. If that posture did not exist I would not have shipped this as-is.
|
|
120
|
+
2. **Could the untrusted text leak by a route I did not consider?** The context is
|
|
121
|
+
an explicit allowlist of derived values, not a filtered copy of the input, so
|
|
122
|
+
a new input field cannot appear in the row by default. The leak tests assert
|
|
123
|
+
absence of planted secrets rather than presence of expected keys, which is the
|
|
124
|
+
direction that catches accidental widening.
|
|
125
|
+
|
|
126
|
+
Tests: 11 new unit tests; 72 green across the affected suites.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Side-effects review — reminder-agent-awareness
|
|
2
|
+
|
|
3
|
+
**Change:** add the dated check-in reminder (ACT-724 step 1, merged in #1630) to
|
|
4
|
+
the agent CLAUDE.md template, with a SEPARATE `PostUpdateMigrator` arm so
|
|
5
|
+
existing agents receive it, plus tests pinning both halves.
|
|
6
|
+
|
|
7
|
+
**Why:** #1630 shipped routes, a job, and 55 tests — and nothing in the agent
|
|
8
|
+
template. Per the Agent Awareness Standard, "an agent that doesn't know about a
|
|
9
|
+
capability effectively doesn't have it." No agent would ever set `checkInAt`, so
|
|
10
|
+
the reconciler would scan an empty set forever and report success doing it.
|
|
11
|
+
|
|
12
|
+
**Tier:** 1 — template text, one migration arm, tests. No runtime behaviour.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 1. Over-block — what legitimate inputs does this reject that it shouldn't?
|
|
17
|
+
|
|
18
|
+
Nothing. This adds documentation and one idempotent migration branch. No
|
|
19
|
+
predicate, no gate, no rejection path.
|
|
20
|
+
|
|
21
|
+
## 2. Under-block — what failure modes does this still miss?
|
|
22
|
+
|
|
23
|
+
- **The Agent Awareness Standard remains unenforced in general.** This change
|
|
24
|
+
gates THIS capability; the next feature can still ship template-less and no
|
|
25
|
+
test will notice. That is the real defect and it is bigger than this PR — the
|
|
26
|
+
standard is prose with no structure behind it, which is precisely why I missed
|
|
27
|
+
it while shipping a feature about not relying on memory. Recorded rather than
|
|
28
|
+
quietly fixed for one case and forgotten.
|
|
29
|
+
- **Drifted CLAUDE.md files get an append, not a placement.** If an agent
|
|
30
|
+
customised the section wording, the marker won't match and the subsection is
|
|
31
|
+
appended near the block instead of at the intended point. Deliberate: a
|
|
32
|
+
misplaced paragraph beats a permanently unaware agent.
|
|
33
|
+
- **The template text can still drift from the shipped routes.** Nothing
|
|
34
|
+
cross-checks the documented curl against the real route path. A rename would
|
|
35
|
+
leave the briefing confidently wrong.
|
|
36
|
+
|
|
37
|
+
## 3. Level-of-abstraction fit
|
|
38
|
+
|
|
39
|
+
Right layer, and the migration arm placement is the substantive decision.
|
|
40
|
+
|
|
41
|
+
The existing Commitments arm is guarded by
|
|
42
|
+
`if (!content.includes('**Commitments & Follow-Through**'))`. Adding the
|
|
43
|
+
subsection inside that branch would reach FRESH INSTALLS ONLY — every currently
|
|
44
|
+
running agent already has that section and never re-enters the branch. That is
|
|
45
|
+
the exact Migration Parity failure mode the standard names, and it looks
|
|
46
|
+
completely correct in a diff.
|
|
47
|
+
|
|
48
|
+
Hence a separate arm keyed on "section present AND subsection absent", with a
|
|
49
|
+
test asserting that separation rather than trusting it.
|
|
50
|
+
|
|
51
|
+
## 4. Signal vs authority compliance
|
|
52
|
+
|
|
53
|
+
`docs/signal-vs-authority.md`. No authority: documentation plus an idempotent
|
|
54
|
+
text patch. The migration decides only whether text is already present — a
|
|
55
|
+
`String.includes` check at a dev/update chokepoint, not a runtime judgment.
|
|
56
|
+
|
|
57
|
+
## 5. Interactions
|
|
58
|
+
|
|
59
|
+
- **The Commitments migration arm** — untouched; the new arm runs after it and
|
|
60
|
+
is reachable in the state the old arm skips.
|
|
61
|
+
- **Shadow-capability slicing** — the subsection sits INSIDE the existing
|
|
62
|
+
Commitments block rather than creating a new top-level marker, so the slicer's
|
|
63
|
+
boundaries are unchanged.
|
|
64
|
+
- **`feature-delivery-completeness`** — passes (126 tests); this change adds
|
|
65
|
+
awareness for a feature that suite already tracks.
|
|
66
|
+
|
|
67
|
+
## 6. External surfaces
|
|
68
|
+
|
|
69
|
+
- **The agent-facing CLAUDE.md gains one subsection.** That IS the point: it is
|
|
70
|
+
the surface through which an agent discovers the capability.
|
|
71
|
+
- No route, config key, flag, env var, or runtime behaviour.
|
|
72
|
+
- The text states the honest scope — dark by default, at-least-once (not
|
|
73
|
+
exactly-once), and that "structurally impossible to have a dated promise
|
|
74
|
+
without a reminder" is NOT yet true. A briefing that oversells produces an
|
|
75
|
+
agent confidently relying on something that isn't running.
|
|
76
|
+
|
|
77
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
78
|
+
|
|
79
|
+
**Unified.** The template is a shipped source constant and the migration runs
|
|
80
|
+
per-machine against each machine's own CLAUDE.md, producing identical text
|
|
81
|
+
everywhere. No new state, no notice, no generated URL, nothing to strand on a
|
|
82
|
+
topic transfer.
|
|
83
|
+
|
|
84
|
+
## 8. Rollback cost
|
|
85
|
+
|
|
86
|
+
Revert. Agents that already received the subsection keep harmless extra
|
|
87
|
+
documentation; the idempotency check means a re-run after revert re-adds it
|
|
88
|
+
rather than duplicating.
|
|
89
|
+
|
|
90
|
+
## Second-pass review
|
|
91
|
+
|
|
92
|
+
**Not required** — no block/allow decision, no session lifecycle, no gate.
|
|
93
|
+
|
|
94
|
+
Self-review, the one thing I went looking for: **did I put the migration where it
|
|
95
|
+
would actually run?** I nearly appended to the Commitments arm, which would have
|
|
96
|
+
been invisible in review and would have reached zero existing agents. The test
|
|
97
|
+
`that arm is SEPARATE from the Commitments-section arm` exists specifically
|
|
98
|
+
because I caught myself about to do the wrong thing, and a comment saying "don't
|
|
99
|
+
do that" would not have survived the next edit.
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
## Addendum — the migration emitted a literal placeholder
|
|
103
|
+
|
|
104
|
+
CI rejected the first version, correctly.
|
|
105
|
+
|
|
106
|
+
The subsection's example commands carry the agent's port, which the surrounding
|
|
107
|
+
template literal interpolates. I authored the section inside a shell heredoc and
|
|
108
|
+
escaped the interpolation so the shell would not consume it — and the escape
|
|
109
|
+
survived into the TypeScript. A patched CLAUDE.md would therefore have contained
|
|
110
|
+
the literal placeholder text instead of a port number, making **every curl
|
|
111
|
+
example in the new section copy-paste-broken**, in the one document agents rely
|
|
112
|
+
on to know how to call things.
|
|
113
|
+
|
|
114
|
+
Caught by `tests/unit/PostUpdateMigrator-coordinationAwareness.test.ts`, which
|
|
115
|
+
asserts no literal placeholder survives into a patched file. That guard exists
|
|
116
|
+
because this has happened before; it earned its keep.
|
|
117
|
+
|
|
118
|
+
**The generalizable bit:** authoring template-literal content through a shell
|
|
119
|
+
heredoc puts two escaping regimes in series, and the second one is invisible in
|
|
120
|
+
the diff — the TypeScript looks correct in isolation. Worth writing such content
|
|
121
|
+
with a file-write tool rather than a heredoc when it contains interpolations.
|
|
122
|
+
|
|
123
|
+
Side effects of the fix: none beyond the corrected string. No test was weakened;
|
|
124
|
+
the existing guard is what caught it.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Side effects — spec round 10 (scanState vs row presence)
|
|
2
|
+
|
|
3
|
+
- **No code changes in this PR.** Spec text only; the feature is dark on every install.
|
|
4
|
+
- **Downstream obligation created:** the Increment-2 route (merged behavior) currently returns zero
|
|
5
|
+
rows on a machine that has never scanned. Once this amendment lands, that behavior contradicts the
|
|
6
|
+
spec and must be fixed — tracked for Increment 3, with the failing input stated in the spec so the
|
|
7
|
+
fix is verifiable rather than asserted.
|
|
8
|
+
- **Risk if NOT amended:** on any machine whose doorway-scan job is disabled (the shipped default),
|
|
9
|
+
the read surface would report nothing while holding a catalog it had already derived — the exact
|
|
10
|
+
"empty reads as unknown" conflation the three-way `scanState` exists to prevent.
|
|
11
|
+
- **Rollback:** revert the spec paragraph; no deployed behavior changes either way.
|
|
12
|
+
- **Provenance note:** this ambiguity was mine as the converging reviewer, not a build defect — the
|
|
13
|
+
implementation followed the spec as written. Recorded so the review history stays honest.
|