instar 1.3.969 → 1.3.971
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/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +33 -23
- package/dist/commands/init.js.map +1 -1
- package/dist/core/CartographerSweepEngine.js +1 -1
- package/dist/core/CartographerSweepEngine.js.map +1 -1
- package/dist/core/CartographerTree.d.ts +5 -2
- package/dist/core/CartographerTree.d.ts.map +1 -1
- package/dist/core/CartographerTree.js +6 -3
- package/dist/core/CartographerTree.js.map +1 -1
- package/dist/core/cartographerDetect.d.ts +1 -1
- package/dist/core/cartographerDetect.d.ts.map +1 -1
- package/dist/core/cartographerDetect.js +2 -2
- package/dist/core/cartographerDetect.js.map +1 -1
- package/dist/server/routes.js +1 -1
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/scripts/cartographer-freshness.mjs +18 -3
- package/src/data/builtin-manifest.json +73 -73
- package/src/scaffold/templates/jobs/instar/reflection-trigger.md +3 -1
- package/upgrades/1.3.970.md +33 -0
- package/upgrades/1.3.971.md +57 -0
- package/upgrades/side-effects/evo007-canonical-source-auth.eli16.md +47 -0
- package/upgrades/side-effects/evo007-canonical-source-auth.md +147 -0
- package/upgrades/side-effects/honest-denominators.md +135 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
Follow-up to the built-in job-template auth fix. That change repaired the shipped `.md` templates; this repairs the **canonical source that generates them**. `src/scaffold/templates/jobs/instar/*.md` are produced from `getDefaultJobs()` in `src/commands/init.ts` via `scripts/regen-default-job-templates.mjs`, so the earlier fix was applied to generated output while the generator's input stayed broken — a regen would have silently reverted it (confirmed with the generator's `--dry-run`, which lists all five previously-fixed templates as regeneration targets).
|
|
9
|
+
|
|
10
|
+
- **13 unauthenticated calls** in canonical job definitions now carry `Authorization: Bearer $AUTH` + `X-Instar-AgentId`. Twelve mirror the prior fix; the thirteenth — `feedback-retry` → `POST /feedback/retry` — was found by the new guard and confirmed live as a 401.
|
|
11
|
+
- **12 config-only token reads** (`AUTH=$(python3 … config.json …)`) replaced with the env-first `${INSTAR_AUTH_TOKEN:-…}` form, across `reflection-trigger`, `memory-export`, `capability-audit`, `identity-review`, `commitment-detection`, and six CLAUDE.md-template instruction blocks. A config-only read is rejected outright on an agent whose stored token has drifted from the running server's.
|
|
12
|
+
- **The reflection activity digest** (ACT-620) fixed in both callsites, and it was worse than reported: in the `.md` the jq program is a **compile error** (interpolation backslashes lost), silenced by `2>/dev/null`, so every reflection ran on an empty digest. The filter excluded `job-start`/`job-queued`, which never occur — real types are `job_triggered` / `job_gate_skip` / `job_skipped` — and the text slot read `.message`/`.title`/`.session_name`/`.slug`, none of which exist; the real keys are `.summary` and `.metadata.slug`. Now filters the real noise types, reads the real keys, and prints a type-count summary so job volume is visible without consuming the 100-line budget.
|
|
13
|
+
- **The auth lint now covers the canonical source**, by resolving `getDefaultJobs(4042)` and linting the real `gate` + `execute.value` strings rather than regexing file text.
|
|
14
|
+
|
|
15
|
+
## What to Tell Your User
|
|
16
|
+
|
|
17
|
+
Reflection runs have been producing an empty activity digest — every reflection was working from a blank page, and the failure was silenced. Reflections will now see real recent activity. As with the prior fix, jobs that were quiet because they were failing will start producing output; that is the repair, not a new fault.
|
|
18
|
+
|
|
19
|
+
## Summary of New Capabilities
|
|
20
|
+
|
|
21
|
+
No new user-facing capability. Thirteen job bodies go from silently 401-ing to functional, reflection digests go from empty to populated, and the build-time guard now covers the source that generates the shipped templates.
|
|
22
|
+
|
|
23
|
+
## Evidence
|
|
24
|
+
|
|
25
|
+
- Guard proven to bite: with `init.ts` reverted to `origin/main`, the canonical-source test fails listing 13 unauthenticated calls (`reflection-trigger`, `feedback-retry`, `insight-harvest`, `evolution-overdue-check`, `evolution-proposal-evaluate`, `evolution-proposal-implement`); the config-only-token test fails with 12. Both pass on the fixed tree.
|
|
26
|
+
- The guard's own first version passed **vacuously** — it regexed raw `init.ts`, whose bodies carry `localhost:\${INSTAR_PORT:-${port}}`, which its pattern never matched. Rewritten to lint resolved job objects, with a sample-size assertion so an empty resolution can never read as clean. The rewrite then found the `feedback-retry` defect.
|
|
27
|
+
- Live: unauthenticated `POST /feedback/retry` returns `{"error":"Missing or invalid Authorization header"}`; authenticated returns `{"ok":true,"retried":0,"succeeded":0}`.
|
|
28
|
+
- Shipped jq confirmed a compile error against a real activity log; corrected jq emits real `scheduler_start`/`scheduler_stop` rows with summaries plus `462 job_triggered / 33 job_gate_skip / 3 scheduler_start / 2 scheduler_stop`.
|
|
29
|
+
- Real log census: keys are `type`, `timestamp`, `summary`, `metadata`, `sessionId`; types are `job_triggered`, `job_gate_skip`, `job_skipped`, `scheduler_start`, `scheduler_stop`.
|
|
30
|
+
- Generated bash for the reflection echo extracted from the resolved job object and executed: renders `-d '{"type":"quick"}'` with `$INSTAR_AUTH_TOKEN` left unexpanded.
|
|
31
|
+
- `tsc --noEmit` clean; auth lint (11 tests), `default-jobs-valid`, `refresh-jobs` green.
|
|
32
|
+
|
|
33
|
+
Remaining general byte-parity between generated templates and their generator source is tracked as ACT-1263 <!-- tracked: ACT-1263 --> rather than folded in — reconciling all 14 generated templates is a distinct change with its own review surface.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
Two of our own checks were announcing results they had not earned. Both are now honest.
|
|
9
|
+
|
|
10
|
+
**The commit gate was not blocking anything.** `.husky/pre-commit` ran its checks as a plain
|
|
11
|
+
sequence with no `set -e`, so the hook's exit status was whatever the *last* command returned.
|
|
12
|
+
`scripts/instar-dev-precommit.js` would correctly refuse a commit — printing its full
|
|
13
|
+
`commit BLOCKED` banner and exiting 1 — and then three further checks ran after it, each exiting
|
|
14
|
+
0, so git saw 0 and accepted the commit anyway. Every blocking check in that hook except the final
|
|
15
|
+
one was effectively advisory. Fixed with `set -e`, with the reasoning recorded inline so it is not
|
|
16
|
+
re-introduced.
|
|
17
|
+
|
|
18
|
+
**A freshness score of 100% for having measured nothing.** `freshRatio` returned `1` when there
|
|
19
|
+
was no denominator — an explicit choice in code (`ratioDenom === 0 ? 1`), not an oversight. Since
|
|
20
|
+
the CI ratchet gates on `freshRatio < FLOOR`, a perfect 1 passed every possible floor, leaving the
|
|
21
|
+
guard structurally unable to fail on an empty map — precisely the case it exists to catch. All
|
|
22
|
+
five producers now return `null` when there is nothing to divide by, and the ratchet distinguishes
|
|
23
|
+
*no authored state at all* (the legitimate case — still passes, but now prints
|
|
24
|
+
`NOT ASSESSED — this check gated on nothing` instead of reporting success) from *an authored index
|
|
25
|
+
containing zero nodes* (a hard failure). A freshly-scaffolded index whose nodes are still within
|
|
26
|
+
their grace period remains a pass.
|
|
27
|
+
|
|
28
|
+
## What to Tell Your User
|
|
29
|
+
|
|
30
|
+
Nothing changes in how you work with your agent, and no action is needed.
|
|
31
|
+
|
|
32
|
+
One small visible difference if you look at the documentation-map health figure: where it
|
|
33
|
+
previously showed 100% freshness for a map with nothing in it, it now shows no figure at all,
|
|
34
|
+
because "we have not measured anything" and "everything is fresh" are different answers and should
|
|
35
|
+
not look identical.
|
|
36
|
+
|
|
37
|
+
## Summary of New Capabilities
|
|
38
|
+
|
|
39
|
+
No new capabilities. Both changes make existing machinery do what it already claimed to do —
|
|
40
|
+
one restores a verdict that was being computed correctly and then discarded, the other stops a
|
|
41
|
+
health figure from manufacturing a passing grade out of an absence of evidence.
|
|
42
|
+
|
|
43
|
+
## Evidence
|
|
44
|
+
|
|
45
|
+
- The defect was proved rather than inferred: a commit was deliberately landed through a printed
|
|
46
|
+
`commit BLOCKED` banner (`b2efded2f`, since reverted). After the fix, the identical commit is
|
|
47
|
+
refused with `husky - pre-commit script failed (code 1)`.
|
|
48
|
+
- Every command in the pre-commit sequence was measured independently on a clean tree with real
|
|
49
|
+
dependencies: all exit 0, so making the hook honest does not turn a green repo red.
|
|
50
|
+
- Freshness: pointing the ratchet at an index that exists and is empty now fails with
|
|
51
|
+
`could NOT be assessed (this is not a pass)`; with no authored state it still exits 0 but says
|
|
52
|
+
so out loud.
|
|
53
|
+
- Three new regression tests cover both sides of the boundary, including one asserting that an
|
|
54
|
+
empty authored index cannot satisfy even a floor of 0. 103 cartographer tests pass; lint clean.
|
|
55
|
+
- One pre-existing E2E assertion required `freshRatio` to be a *number* on an empty tree — it was
|
|
56
|
+
protecting the old behaviour and would have failed anyone fixing this honestly. It now asserts
|
|
57
|
+
the honest contract, with the reason recorded inline.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Fixing the thing that generates the job instructions — Plain-English Overview
|
|
2
|
+
|
|
3
|
+
> The one-line version: last change fixed the printout; this one fixes the printer, because the printer would have re-printed the broken version.
|
|
4
|
+
|
|
5
|
+
## The problem in one breath
|
|
6
|
+
|
|
7
|
+
The previous fix repaired six job instruction files. It turns out those files are **generated** from a master list elsewhere in the code — and that master list was never fixed. Anyone regenerating the files would have quietly undone the repair, and everything would still *look* fine.
|
|
8
|
+
|
|
9
|
+
That is the same trap this whole family of bugs keeps setting: a change that looks exactly like a real fix until something routine wipes it.
|
|
10
|
+
|
|
11
|
+
## What already exists
|
|
12
|
+
|
|
13
|
+
- **The master list** — one place in the code that defines every built-in scheduled job: when it runs, what it checks, and the instructions it follows.
|
|
14
|
+
- **The generator** — a small script that turns that master list into the individual instruction files that ship to your agent.
|
|
15
|
+
- **The build-time check from last time** — scans the *generated* files for jobs that call a locked door without a key.
|
|
16
|
+
|
|
17
|
+
## What this adds
|
|
18
|
+
|
|
19
|
+
**The check now also reads the master list**, not just the files it produces. That's the actual fix: the previous check guarded the output while the input stayed broken.
|
|
20
|
+
|
|
21
|
+
Along with that:
|
|
22
|
+
|
|
23
|
+
- **Thirteen jobs** in the master list now carry the key. Twelve were already known. The thirteenth — a job that retries failed feedback delivery — was found *by the new check*, not by me, and confirmed broken.
|
|
24
|
+
- **Twelve places** were reading the key from a stored copy on disk instead of the live one. On a machine where the stored copy has gone out of date, that copy is rejected. They now prefer the live one.
|
|
25
|
+
- **The reflection job's activity summary was completely empty** — and worse than reported. Its filter looked for two event types that never occur, and it read four field names that don't exist. On top of that, the version in the shipped file had lost some punctuation, which made it not just wrong but a **syntax error** — silenced, so it failed invisibly. Every reflection has been running on a blank page. Now it filters the event types that actually occur, reads the fields that actually exist, and adds a short count summary so the reflection can see how busy things were.
|
|
26
|
+
|
|
27
|
+
## The safeguards
|
|
28
|
+
|
|
29
|
+
**The check was caught lying, and fixed.** Its first version passed cleanly against the broken master list — not because the list was fine, but because its pattern didn't match the way text is written there. A check that passes for the wrong reason is worse than no check, because it hands you confidence you didn't earn. It now reads the *actual resolved jobs* rather than pattern-matching source text, and it refuses to report "clean" if it somehow finds no jobs to inspect.
|
|
30
|
+
|
|
31
|
+
**It immediately proved itself.** The rewritten check found a broken job I didn't know about. That's the difference between a check that describes a fix and one that does work.
|
|
32
|
+
|
|
33
|
+
**It was proven against the broken version first.** Turn the fix off, and the check reports all thirteen problems. Turn it on, and it reports none.
|
|
34
|
+
|
|
35
|
+
**Documentation examples aren't punished.** The master list file also contains example commands written for humans to read. Those are deliberately not treated as real jobs, so the check doesn't flag prose.
|
|
36
|
+
|
|
37
|
+
## What ships when
|
|
38
|
+
|
|
39
|
+
All together — the master-list fixes, the reflection digest repair, and the widened check.
|
|
40
|
+
|
|
41
|
+
## What's deliberately left for later
|
|
42
|
+
|
|
43
|
+
The generated files and the master list have drifted apart over time in other, harmless-looking ways. The **key-and-lock** problem is now closed on both sides, but a full reconciliation of all fourteen generated files is a separate job with its own review. It's written down and tracked rather than quietly forgotten.
|
|
44
|
+
|
|
45
|
+
## If it goes wrong
|
|
46
|
+
|
|
47
|
+
Undo the change and ship a patch. Nothing is stored, nothing is migrated. Worst case is a return to quiet jobs that don't do anything — the same benign fallback as before.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Side-Effects Review — Canonical job source must authenticate too (EVO-007 follow-up, ACT-620)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `evo007-canonical-source-auth`
|
|
4
|
+
**Date:** `2026-07-25`
|
|
5
|
+
**Author:** `Echo (instar-dev agent)`
|
|
6
|
+
**Second-pass reviewer:** `not required`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
PR #1636 fixed the shipped `.md` job templates. This fixes the thing that **generates** them. `src/scaffold/templates/jobs/instar/*.md` are produced from `getDefaultJobs()` in `src/commands/init.ts` by `scripts/regen-default-job-templates.mjs`, so the previous fix was applied to generated output while the generator's input stayed broken — a regen would have silently reverted it. That is precisely the "fix that only looks like a fix" this family of defects keeps producing, and it was caught by reading the generator rather than trusting the previous PR's completeness.
|
|
11
|
+
|
|
12
|
+
Changes:
|
|
13
|
+
|
|
14
|
+
1. **13 unauthenticated calls** in canonical job definitions now carry `Authorization: Bearer $AUTH` + `X-Instar-AgentId`. Twelve mirror PR #1636; the thirteenth — `feedback-retry` → `POST /feedback/retry` — was **found by the new guard**, not by me, and confirmed live as a 401.
|
|
15
|
+
2. **12 config-only token reads** (`AUTH=$(python3 … config.json …)`) replaced with the env-first `${INSTAR_AUTH_TOKEN:-…}` form across `reflection-trigger`, `memory-export`, `capability-audit`, `identity-review`, `commitment-detection`, and six CLAUDE.md-template instruction blocks. A config-only read is a live defect on any agent whose stored token has drifted from the running server's.
|
|
16
|
+
3. **ACT-620's blind activity digest** fixed in both callsites. The shipped jq was worse than reported: in the `.md` it is a **jq compile error** (interpolation backslashes lost), silenced by `2>/dev/null`, so every reflection ran on an empty digest. The filter also excluded `job-start`/`job-queued`, which never occur — the real types are `job_triggered` / `job_gate_skip` / `job_skipped` — and the text slot read `.message`/`.title`/`.session_name`/`.slug`, none of which are keys; the real ones are `.summary` and `.metadata.slug`. Now filters the real noise types, reads the real keys, and adds a volume summary so the reflection sees job activity without it eating the 100-line budget.
|
|
17
|
+
4. **The lint now covers the canonical source**, by resolving `getDefaultJobs(4042)` and linting the real `gate` + `execute.value` strings.
|
|
18
|
+
|
|
19
|
+
## Decision-point inventory
|
|
20
|
+
|
|
21
|
+
- `tests/unit/job-template-auth-lint.test.ts` — **modify** — extended to the canonical source. Still a CI lint: build-time only, zero runtime authority.
|
|
22
|
+
- `getDefaultJobs()` job bodies — **modify** — LLM instruction text and shell one-liners. No block/allow logic.
|
|
23
|
+
- No runtime module, route, gate, or sentinel is touched.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 1. Over-block
|
|
28
|
+
|
|
29
|
+
The only rejecting surface is the lint. Its over-block risk is a false CI failure.
|
|
30
|
+
|
|
31
|
+
Notably, the **first version of the canonical-source guard was worse than a false positive — it was a false NEGATIVE**: it regexed the raw `init.ts` text, whose bodies contain `localhost:\${INSTAR_PORT:-${port}}`, which the detector's `\d+`/`$PORT` pattern never matched. It passed **vacuously** while the source was fully broken. Caught by running the detector against the pre-fix source and seeing it report nothing where an independent probe found violations. Fixed by linting the *resolved* job objects instead of the file text, plus an explicit `expect(jobs.length).toBeGreaterThan(10)` so an empty/failed resolution can never read as "clean".
|
|
32
|
+
|
|
33
|
+
Scoping the lint to resolved job objects also removes a real over-block: `init.ts` embeds the CLAUDE.md template prose, which contains illustrative `curl` examples that are documentation, not job bodies. Linting the whole file flagged 52 of those.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 2. Under-block
|
|
38
|
+
|
|
39
|
+
- Same line-scoped-curl and non-curl-client limits as PR #1636.
|
|
40
|
+
- The lint verifies a header is *present*, not that the token *resolves*; the config-only rule covers the known stale-token shape but a novel wrong-token expression would pass.
|
|
41
|
+
- **The `.md` files and `init.ts` are still not byte-compared.** The auth/token class is now closed on both sides, but general drift is not — tracked as **ACT-1263** <!-- tracked: ACT-1263 -->.
|
|
42
|
+
- The jq fix is verified against this machine's activity logs. A log containing event types not present here would still be filtered by name.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 3. Level-of-abstraction fit
|
|
47
|
+
|
|
48
|
+
Correct layer, and this change *moves* the guard to the right layer: the previous lint sat on the generated artifact, one level below where the defect is authored. Linting the generator's resolved output is the level at which "a shipped job body must authenticate" is actually decidable.
|
|
49
|
+
|
|
50
|
+
Resolving `getDefaultJobs()` rather than regexing the source is the same principle applied again — ask the system for its real answer instead of pattern-matching its text.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 4. Signal vs authority compliance
|
|
55
|
+
|
|
56
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
57
|
+
|
|
58
|
+
- [x] No — this change has no block/allow surface.
|
|
59
|
+
|
|
60
|
+
The lint can only fail a build. The job bodies are instructions and shell strings with no decision logic. Nothing here can refuse an agent action at runtime.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 4b. Judgment-point check (Judgment Within Floors standard)
|
|
65
|
+
|
|
66
|
+
**No new static heuristic at a competing-signals decision point.** "Does this resolved job body call a non-public endpoint without an auth header?" is an invariant over a finite, enumerable public-path set. No competing live signals.
|
|
67
|
+
|
|
68
|
+
The jq event-type filter is a static list, but it is not a decision point either — it selects log lines for a human-readable digest, and a mis-filtered line degrades digest quality, never a decision.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 5. Interactions
|
|
73
|
+
|
|
74
|
+
- **Shadowing:** None. The lint file is independent.
|
|
75
|
+
- **Double-fire:** None.
|
|
76
|
+
- **Races:** None.
|
|
77
|
+
- **Feedback loops:** `reflection-trigger`'s digest feeds an LLM reflection that may write MEMORY.md. Fixing the digest changes that input from *empty* to *real*, which is the intent; the digest is bounded (`tail -100` plus a type-count summary) so it cannot grow unbounded.
|
|
78
|
+
- **Generator interaction (the important one):** `regen-default-job-templates.mjs` writes `.md` from these bodies. Because the canonical source is now fixed, a regen propagates the fix instead of reverting it. Verified via `--dry-run`, which lists all five previously-fixed templates as regeneration targets — the concrete proof the prior fix alone was revertible.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 6. External surfaces
|
|
83
|
+
|
|
84
|
+
- **Other users of the install base:** 13 job bodies that silently 401'd will start working, and reflection digests go from empty to populated. Same "quiet becomes active" note as PR #1636.
|
|
85
|
+
- **External systems:** none; all calls are localhost.
|
|
86
|
+
- **Persistent state:** none added.
|
|
87
|
+
- **Secrets:** the reflection echo deliberately emits `$INSTAR_AUTH_TOKEN` **unexpanded**, so no token value enters a transcript. Verified by executing the generated line.
|
|
88
|
+
- **Operator surface:** none added or touched.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 6b. Operator-surface quality (Operator-Surface Quality standard)
|
|
93
|
+
|
|
94
|
+
**No operator surface — not applicable.** No dashboard renderer, approval page, or grant/secret form is touched.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
99
|
+
|
|
100
|
+
**Machine-local BY DESIGN**, same reason as PR #1636: templates are installed per machine by that machine's own `installBuiltinJobs()`, and the token each body resolves is necessarily machine-local (every install holds its own `authToken`; a shared token cannot work cross-machine). Identical shipped content resolved against per-machine credentials — not machine-local state that ought to be replicated.
|
|
101
|
+
|
|
102
|
+
- **User-facing notices:** none emitted by this change.
|
|
103
|
+
- **Durable state on topic transfer:** none held.
|
|
104
|
+
- **Generated URLs:** none; all are localhost calls made by the job on its own machine.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 8. Rollback cost
|
|
109
|
+
|
|
110
|
+
- **Hot-fix release:** revert the commit; agents pick it up on their next update.
|
|
111
|
+
- **Data migration:** none.
|
|
112
|
+
- **Agent state repair:** none.
|
|
113
|
+
- **User visibility during rollback:** reverting restores quiet failure — no crash, no data loss. Same benign asymmetry as PR #1636.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Conclusion
|
|
118
|
+
|
|
119
|
+
This review's main product is the correction of my own guard. The canonical-source lint initially passed vacuously — the worst failure mode available to a check, because a vacuous pass is indistinguishable from a real one and would have shipped as false assurance. Testing the guard against the pre-fix source (rather than only against the fixed tree) exposed it, and the rewrite to lint resolved job objects then immediately earned its place by finding a 13th defect, `feedback-retry`, that I had not known about.
|
|
120
|
+
|
|
121
|
+
The change is clear to ship. The remaining known gap — general byte-parity between generated templates and their source — is tracked as ACT-1263 rather than folded in, because reconciling all 14 generated templates is a distinct change with its own review surface.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Second-pass review (if required)
|
|
126
|
+
|
|
127
|
+
**Reviewer:** not required. No block/allow surface, no session lifecycle, no context/compaction, no coherence/trust surface, and no sentinel/guard/gate/watchdog runtime component. The only "gate"-adjacent text is the `gate:` frontmatter field, whose *content* is edited but whose evaluation semantics are untouched.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Evidence pointers
|
|
132
|
+
|
|
133
|
+
- Guard proven to bite: with `init.ts` reverted to `origin/main`, the canonical-source test fails listing 13 unauthenticated calls across `reflection-trigger`, `feedback-retry`, `insight-harvest`, `evolution-overdue-check`, `evolution-proposal-evaluate`, `evolution-proposal-implement`; the config-only-token test fails with 12. Both pass on the fixed tree.
|
|
134
|
+
- Live 401 → 200 for the newly-found endpoint: unauthenticated `POST /feedback/retry` → `{"error":"Missing or invalid Authorization header"}`; authenticated → `{"ok":true,"retried":0,"succeeded":0}`.
|
|
135
|
+
- Shipped jq proven to be a compile error against a real activity log; corrected jq emits real `scheduler_start`/`scheduler_stop` rows with summaries, and the volume summary reports `462 job_triggered / 33 job_gate_skip / 3 scheduler_start / 2 scheduler_stop`.
|
|
136
|
+
- Real log key/type census: top-level keys are `type`, `timestamp`, `summary`, `metadata`, `sessionId` (never `message`/`title`/`session_name`/`slug`); types are `job_triggered`, `job_gate_skip`, `job_skipped`, `scheduler_start`, `scheduler_stop` (never `job-start`/`job-queued`).
|
|
137
|
+
- Generated bash for the reflection echo extracted from the resolved job object and executed: renders `-d '{"type":"quick"}'` with `$INSTAR_AUTH_TOKEN` unexpanded.
|
|
138
|
+
- `tsc --noEmit` clean; lint (11 tests), `default-jobs-valid`, `refresh-jobs` green.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Class-Closure Declaration (display-only mirror)
|
|
143
|
+
|
|
144
|
+
- **`defectClass`** — `proxy-signal-substitution` (the same class as PR #1636 and the EVO-004/005/006 family): the generated artifact was treated as the thing to fix, when the generator's input is what determines what ships.
|
|
145
|
+
- **`closure`** — `guard`.
|
|
146
|
+
- **`guardEvidence`** — `{enforcementType: lint, citation: tests/unit/job-template-auth-lint.test.ts#"the canonical source (getDefaultJobs) authenticates its non-public API calls", howCaught: it resolves getDefaultJobs(4042) and lints the real gate + prompt bodies, so an unauthenticated call cannot enter the canonical source that generates the shipped templates; run against the pre-fix source it reports all 13, and a sample-size assertion prevents a vacuous pass}`.
|
|
147
|
+
- **`gap`** — `ACT-1263` for the remaining general byte-parity between generated templates and their generator source, which this change does not close.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Side-Effects Review — honest denominators: a gate that discards its verdict, and a ratio that reports 1 over nothing
|
|
2
|
+
|
|
3
|
+
## Summary of the change
|
|
4
|
+
|
|
5
|
+
Two related honesty defects, both found by the convergence-towards-coherence audit (2026-07-25).
|
|
6
|
+
|
|
7
|
+
**A. `.husky/pre-commit` discarded its own blocking verdict.** The hook was a plain command
|
|
8
|
+
sequence with no `set -e`, so its exit status was the LAST command's.
|
|
9
|
+
`scripts/instar-dev-precommit.js` exits 1 and prints a full "commit BLOCKED" banner; three checks
|
|
10
|
+
then run after it, each exiting 0, so git saw 0 and accepted the commit. Verified by landing a
|
|
11
|
+
commit through a printed block (`b2efded2f`, reverted). Every blocking check in the hook except
|
|
12
|
+
the final one was advisory. Fix: `set -e`, with the reasoning recorded inline.
|
|
13
|
+
|
|
14
|
+
**B. `freshRatio` reported a perfect `1` when there was nothing to divide by.** Explicit in code
|
|
15
|
+
(`ratioDenom === 0 ? 1`), not an oversight. The CI ratchet compares `freshRatio < FLOOR`, so 1
|
|
16
|
+
passed every possible floor — the guard was structurally unable to fail on an empty map, the exact
|
|
17
|
+
case it exists to catch. Fix: all five producers return `null` with no denominator; the ratchet
|
|
18
|
+
distinguishes *no authored state* (legitimate CI case → still exit 0, but prints
|
|
19
|
+
`NOT ASSESSED — this check gated on nothing`) from *an authored index with zero nodes*
|
|
20
|
+
(→ hard failure).
|
|
21
|
+
|
|
22
|
+
## Decision-point inventory
|
|
23
|
+
|
|
24
|
+
- `.husky/pre-commit` — a commit-time authority. Change makes its existing verdict effective; it
|
|
25
|
+
adds no new verdict of its own.
|
|
26
|
+
- `scripts/cartographer-freshness.mjs --check` — a CI-time authority. Change adds one failure
|
|
27
|
+
condition (authored index with zero nodes) and one explicit non-assessment notice.
|
|
28
|
+
- The five `freshRatio` producers are pure reporters; no decision logic changed.
|
|
29
|
+
|
|
30
|
+
## 1. Over-block
|
|
31
|
+
|
|
32
|
+
**A.** `set -e` means the FIRST failing check aborts, so later checks do not run and a developer
|
|
33
|
+
sees one problem at a time rather than all of them. Accepted: that is standard hook behaviour and
|
|
34
|
+
strictly better than the previous state (no check could block at all). Every command in the
|
|
35
|
+
sequence is intended to block; the only conditional (`if node -e … fi`) is a shell condition,
|
|
36
|
+
which `set -e` deliberately does not trip on.
|
|
37
|
+
|
|
38
|
+
**B.** The narrow risk was failing a legitimately-not-yet-assessable index. Caught during build:
|
|
39
|
+
my first version failed a freshly-scaffolded tree whose nodes are all within grace, breaking two
|
|
40
|
+
pre-existing tests. Narrowed to `nodeCount === 0`. A scaffold within grace is a pass, and the
|
|
41
|
+
existing `neverAuthoredPastGrace` ceiling still guards the backlog case.
|
|
42
|
+
|
|
43
|
+
## 2. Under-block
|
|
44
|
+
|
|
45
|
+
**A.** `--no-verify` still bypasses everything — unchanged and out of scope; the skill already
|
|
46
|
+
names bypass as an anti-pattern. Server-side CI remains the backstop.
|
|
47
|
+
|
|
48
|
+
**B.** A *populated but stale* map is still governed only by the configured floor, which ships at
|
|
49
|
+
`0` by default and therefore still gates weakly. This change does not raise that floor — doing so
|
|
50
|
+
is a separate judgement about desired strictness, and bundling it would hide which change caused
|
|
51
|
+
which effect. Named here rather than silently left.
|
|
52
|
+
|
|
53
|
+
## 3. Level-of-abstraction fit
|
|
54
|
+
|
|
55
|
+
Both fixes are at the layer that already owns the decision. **A** repairs how an existing verdict
|
|
56
|
+
is propagated — it does not move authority. **B** repairs what a reporter reports and how its
|
|
57
|
+
existing consumer reads "no evidence". Neither adds a parallel checker beside a smarter one.
|
|
58
|
+
|
|
59
|
+
## 4. Signal vs authority compliance
|
|
60
|
+
|
|
61
|
+
Compliant, and the change moves *toward* the principle.
|
|
62
|
+
|
|
63
|
+
- **A** adds no new authority. The authority (`instar-dev-precommit.js`) already existed and
|
|
64
|
+
already computed the right answer; the defect was that its signal was discarded. Making an
|
|
65
|
+
existing verdict effective is the opposite of granting brittle logic new blocking power.
|
|
66
|
+
- **B** the producers remain pure signals (`number | null`). The one authority (the CI ratchet)
|
|
67
|
+
gains a failure condition that is *deterministic and evidence-based* — it fires only on the
|
|
68
|
+
objective state "an index exists and contains zero nodes". Crucially it fails toward "cannot
|
|
69
|
+
assess", never toward a fabricated pass, which is the direction the principle asks for.
|
|
70
|
+
|
|
71
|
+
## 4b. Judgment-point check
|
|
72
|
+
|
|
73
|
+
No LLM judgement involved. Both are deterministic checks over objective state (exit codes; node
|
|
74
|
+
counts). No string-matching heuristic gains authority.
|
|
75
|
+
|
|
76
|
+
## 5. Interactions
|
|
77
|
+
|
|
78
|
+
- **A** interacts with every check in the hook: `npm run lint`,
|
|
79
|
+
`lint-migration-consumer-completeness`, `instar-dev-precommit`, `check-rule3-coverage`,
|
|
80
|
+
`protect-migration-guarantee`, `check-e2e-pairing`. All six become genuinely blocking. Verified
|
|
81
|
+
each currently exits 0 on a clean tree with real dependencies installed, so this does not turn a
|
|
82
|
+
currently-green repo red. **This was checked only after a false alarm**: I first measured a lint
|
|
83
|
+
failure and reported that the fix would block every developer — that failure was an artifact of
|
|
84
|
+
my own worktree pointing at an 85-commit-stale `node_modules` that predated the `undici`
|
|
85
|
+
dependency. With a real `npm ci`, lint exits 0. The false alarm is recorded because the
|
|
86
|
+
generalisation-from-local-artifact is the more instructive error.
|
|
87
|
+
- **B** does not shadow the `neverAuthoredPastGrace` or `authorFailed` ceilings; they are
|
|
88
|
+
evaluated independently and unchanged.
|
|
89
|
+
|
|
90
|
+
## 6. External surfaces
|
|
91
|
+
|
|
92
|
+
`GET /cartographer/health` now returns `freshness.freshRatio: null` instead of `1` in the
|
|
93
|
+
no-data case. Any external consumer doing arithmetic on it must handle null. Type-checked across
|
|
94
|
+
the tree: zero errors, because the only pipeline consumer is the `.mjs` ratchet, which is updated
|
|
95
|
+
here. The dashboard renders the freshness block; `null` displays as absent rather than as a
|
|
96
|
+
fabricated 100%, which is the intended user-visible effect.
|
|
97
|
+
|
|
98
|
+
## 6b. Operator-surface quality
|
|
99
|
+
|
|
100
|
+
The ratchet's new no-assessment line is plain English and states the consequence:
|
|
101
|
+
`NOT ASSESSED — no authored cartographer state; this check gated on nothing.` The failure line
|
|
102
|
+
says what could not be done and explicitly that it is not a pass, rather than emitting a bare
|
|
103
|
+
code. No file paths or config syntax are surfaced to a user; both lines are developer/CI-facing.
|
|
104
|
+
|
|
105
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
106
|
+
|
|
107
|
+
**Machine-local by design, both.** `.husky/pre-commit` is a per-checkout git hook — there is no
|
|
108
|
+
cross-machine surface and no replication path is meaningful. The cartographer index is per-machine
|
|
109
|
+
state and its freshness is a property of that machine's checkout; `GET /cartographer/health` is
|
|
110
|
+
already a machine-local read. No durable state strands on topic transfer, no generated URL crosses
|
|
111
|
+
a machine boundary, and no user-facing notice is emitted that would need one-voice gating.
|
|
112
|
+
|
|
113
|
+
## 8. Rollback cost
|
|
114
|
+
|
|
115
|
+
Cheap and total, for both. **A**: delete one line (`set -e`) — restores the previous behaviour
|
|
116
|
+
exactly. **B**: revert the commit; the producers return to `1` and the ratchet to its prior
|
|
117
|
+
comparison. No data migration, no agent state repair, no released artifact depends on the shape.
|
|
118
|
+
The `null` value is additive to a type, not a schema change with stored records behind it.
|
|
119
|
+
|
|
120
|
+
## Conclusion
|
|
121
|
+
|
|
122
|
+
Both changes make an existing mechanism do what it already claimed to do. Neither adds authority;
|
|
123
|
+
one restores authority that was being silently discarded, the other stops a reporter from
|
|
124
|
+
manufacturing a passing grade out of an absence of evidence.
|
|
125
|
+
|
|
126
|
+
The honest note for reviewers: I got the second one wrong twice during the build (over-broad
|
|
127
|
+
failure condition caught by pre-existing tests; a false blast-radius claim caught by checking my
|
|
128
|
+
own environment), and one existing E2E assertion had to be corrected because it was protecting
|
|
129
|
+
the old behaviour. That history is in the commit message and the test comments so it cannot be
|
|
130
|
+
quietly reverted.
|
|
131
|
+
|
|
132
|
+
**Second-pass review: not required.** Per the skill's Phase 5 list, this touches no runtime
|
|
133
|
+
agent-behaviour gate — no outbound/inbound message block/allow, no session lifecycle, no
|
|
134
|
+
compaction/respawn, no coherence gate or trust level. It touches a git hook and a CI script, both
|
|
135
|
+
build-time. The PR is the review surface, per Tier 1.
|