instar 1.3.968 → 1.3.970
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/package.json +1 -1
- package/src/data/builtin-manifest.json +29 -29
- package/src/scaffold/templates/jobs/instar/evolution-overdue-check.md +7 -3
- package/src/scaffold/templates/jobs/instar/evolution-proposal-evaluate.md +7 -3
- package/src/scaffold/templates/jobs/instar/evolution-proposal-implement.md +6 -2
- package/src/scaffold/templates/jobs/instar/identity-review.md +1 -1
- package/src/scaffold/templates/jobs/instar/insight-harvest.md +7 -3
- package/src/scaffold/templates/jobs/instar/reflection-trigger.md +4 -2
- package/upgrades/1.3.969.md +35 -0
- package/upgrades/1.3.970.md +33 -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/evo007-job-template-auth.eli16.md +51 -0
- package/upgrades/side-effects/evo007-job-template-auth.md +160 -0
|
@@ -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,51 @@
|
|
|
1
|
+
# Built-in job templates must authenticate their API calls — Plain-English Overview
|
|
2
|
+
|
|
3
|
+
> The one-line version: five of your scheduled background jobs have been quietly doing nothing for their entire existence, because their instructions told them to knock on a locked door without the key.
|
|
4
|
+
|
|
5
|
+
## The problem in one breath
|
|
6
|
+
|
|
7
|
+
Your agent runs scheduled background jobs — small recurring tasks like "review new improvement proposals" or "harvest lessons learned". Each job has a **doorman check** (does this job have anything to do right now?) and a **body** (the actual instructions). The doorman checks were carrying the agent's key. The instructions were not. So every one of those jobs woke up, correctly decided "yes, there's work here", walked to the door, got refused, and exited — reporting nothing wrong.
|
|
8
|
+
|
|
9
|
+
The reason nobody noticed for so long is the cruel part: these jobs are *designed* to be silent when there's nothing to do. A job that fails silently and a job that succeeds with nothing to report look **exactly the same** from the outside.
|
|
10
|
+
|
|
11
|
+
## What already exists
|
|
12
|
+
|
|
13
|
+
- **The scheduled jobs themselves** — small recurring tasks your agent runs on a timer. They already work as a system; the scheduler, the timing, and the reporting are all fine.
|
|
14
|
+
- **The lock on the agent's own API** — nearly every internal endpoint requires a key (a "bearer token"). A handful of endpoints are deliberately public, like the basic health check.
|
|
15
|
+
- **The key itself** — handed to every job through its environment. It was there the whole time. The instructions just never picked it up.
|
|
16
|
+
- **The update pipeline** — whenever your agent updates, it rewrites its job instruction files from the shipped originals. This matters enormously, and it's covered below.
|
|
17
|
+
|
|
18
|
+
## What this adds
|
|
19
|
+
|
|
20
|
+
**The instructions now pick up the key.** Each of the affected job bodies now starts by resolving the agent's key, its identity, and its port, and every call it makes carries them. That's the whole fix — about thirty lines of text across six files.
|
|
21
|
+
|
|
22
|
+
Alongside the fix, a **build-time check** now scans every shipped job instruction file and fails the build if any of them calls a locked endpoint without the key. Secondary changes:
|
|
23
|
+
|
|
24
|
+
- One more job (`identity-review`) was reading the key from a **stale source**. On this very agent, that stored copy has drifted out of date and is rejected by the server, while the live one works. It now prefers the live one.
|
|
25
|
+
- One job (`reflection-trigger`) had a **second, unrelated bug on the same line**: a quoting mistake that mangled the data it sends into invalid gibberish. Fixed too, since the line was being rewritten anyway.
|
|
26
|
+
|
|
27
|
+
## The new pieces
|
|
28
|
+
|
|
29
|
+
- **The build-time check** — reads every shipped job instruction file, finds every call it makes, and asks two questions: is this endpoint locked, and if so, is the key present? It also refuses instructions that *use* a key they never *fetch*. What it is explicitly **not** allowed to do: anything at runtime. It cannot block your agent, delay a message, or stop a job. It is a test. Its only power is to fail a build before broken instructions ever reach you. That line matters — a check this simple must never be given authority over a live agent.
|
|
30
|
+
|
|
31
|
+
## The safeguards
|
|
32
|
+
|
|
33
|
+
**Prevents the same bug from shipping again.** The check was proven honest before being trusted: run against the *old* files, it reports exactly the twelve broken calls that actually shipped — not eleven, not thirteen. Run against the fixed files, zero. A check that has never been shown to catch the real bug is just decoration.
|
|
34
|
+
|
|
35
|
+
**Prevents the check from crying wolf.** The first draft of the check falsely accused three perfectly healthy job files, because they fetch their key in a slightly different but entirely valid style. That was caught, fixed, and locked down with its own test so it can't come back. A check that fails on healthy files gets switched off by the next frustrated person, which is worse than having no check.
|
|
36
|
+
|
|
37
|
+
**Prevents a fix that only looks like a fix.** This is the trap this change deliberately avoided. Your agent rewrites its job files from the shipped originals on **every update**. So "fixing" the copies sitting on this machine would have worked perfectly — until the next update silently wiped them. That's a fix indistinguishable from a real one, which is exactly the kind of illusion this family of bugs keeps producing. The change is made to the shipped originals, so it reaches every agent through the normal update path and stays fixed.
|
|
38
|
+
|
|
39
|
+
**Prevents guessing about which doors are locked.** The check doesn't keep its own private list of public endpoints — that would drift out of date. It mirrors the real list and asserts the real one still contains those entries, so if a public endpoint ever becomes locked, the check fails loudly instead of quietly waving it through.
|
|
40
|
+
|
|
41
|
+
## What ships when
|
|
42
|
+
|
|
43
|
+
All of it ships together, in one change — the six corrected instruction files and the check that keeps them correct. There's no phased rollout because there's nothing to roll out gradually: the files are either right or wrong, and they're currently wrong.
|
|
44
|
+
|
|
45
|
+
## What you'll actually notice
|
|
46
|
+
|
|
47
|
+
Some jobs that have always been silent will **start producing output**. That's the repair working, not a new problem — but it's worth knowing in advance so a suddenly-chatty proposal-review job doesn't read as a malfunction.
|
|
48
|
+
|
|
49
|
+
## If it goes wrong
|
|
50
|
+
|
|
51
|
+
Undo the six files and ship the patch; agents pick up the reversal on their next update. Nothing is stored, nothing is migrated, nothing needs repairing. The worst case is a return to exactly today's behaviour — quiet jobs that don't do anything. That asymmetry is the honest argument for making this change: there's almost nothing to lose, and five broken jobs to gain back.
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Side-Effects Review — Built-in job templates must authenticate their API calls
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `evo007-job-template-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
|
+
Five shipped built-in job templates carried a `gate:` line that authenticates correctly, but **prompt bodies** that called authenticated endpoints with a bare `curl -s http://localhost:.../evolution/...`. All 12 of those calls return `401 Missing or invalid Authorization header` at runtime. The gate passing is precisely what hid the failure: the job would wake up, correctly conclude "there is work to do", then read nothing and exit "silently" — indistinguishable from a healthy no-op. A sixth template (`identity-review.md`) defined `AUTH` by reading `.instar/config.json` only, which is broken on any agent whose config token has drifted from the live server token (observed first-hand on this agent: the 16-char config value is rejected, the 36-char `$INSTAR_AUTH_TOKEN` is accepted).
|
|
11
|
+
|
|
12
|
+
Files touched: six templates under `src/scaffold/templates/jobs/instar/` (`evolution-overdue-check`, `evolution-proposal-implement`, `evolution-proposal-evaluate`, `insight-harvest`, `reflection-trigger`, `identity-review`), plus a new CI lint at `tests/unit/job-template-auth-lint.test.ts`. Each fixed body now resolves the canonical `AUTH` / `AGENT_ID` / `PORT` block (env-first, config fallback) and passes `Authorization: Bearer $AUTH` + `X-Instar-AgentId: $AGENT_ID` on every non-public call.
|
|
13
|
+
|
|
14
|
+
Two additional defects were found and fixed while in the file, both on the same `reflection-trigger.md` line: the echoed instruction rendered `-d '{type:quick}'` (unescaped inner double quotes were eaten by the enclosing double-quoted `echo`, producing invalid JSON), and it carried no auth. Both are fixed with escaped quoting, verified by executing the echo.
|
|
15
|
+
|
|
16
|
+
## Decision-point inventory
|
|
17
|
+
|
|
18
|
+
- `tests/unit/job-template-auth-lint.test.ts` — **add** — a CI lint (build-time signal). It has no runtime surface and cannot gate, delay, or block any agent action.
|
|
19
|
+
- Job template bodies — **modify** — these are LLM instructions, not code. They do not make block/allow decisions; they instruct the reading agent which HTTP calls to make.
|
|
20
|
+
- `src/server/middleware.ts` `authMiddleware()` — **pass-through** — not modified. The lint mirrors its public-path exemptions and asserts (drift guard) that each exempted path still appears there.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 1. Over-block
|
|
25
|
+
|
|
26
|
+
**What legitimate inputs does this change reject that it shouldn't?**
|
|
27
|
+
|
|
28
|
+
The only rejecting surface is the CI lint, and its over-block risk is a false CI failure on a healthy template. This risk **materialized during development and was fixed**: the first draft anchored the `AUTH=` definition check to line-start (`/^\s*AUTH=/m`) and falsely accused three healthy templates (`initiative-digest-review.md`, `mentor-onboarding.md`, `org-intent-drift-audit.md`) that legitimately define `AUTH` inside backticks as a prose step (`0. **Set auth context:** \`AUTH="..."\``). The detector now matches `AUTH=` not preceded by `$` or a word character, and a regression test (`accepts an AUTH definition presented as a prose step in backticks`) pins the fix.
|
|
29
|
+
|
|
30
|
+
Residual over-block risk: a future template that intentionally calls a non-public endpoint *without* auth (e.g. to demonstrate a 401 in documentation prose) would be flagged. No such template exists today; the fix would be to exempt it explicitly rather than weaken the rule.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 2. Under-block
|
|
35
|
+
|
|
36
|
+
**What failure modes does this still miss?**
|
|
37
|
+
|
|
38
|
+
Concrete and acknowledged:
|
|
39
|
+
|
|
40
|
+
- **Multi-line curl continuations.** The detector scopes a curl invocation to its line. A template using `curl \` + newline + `-H "Authorization: ..."` would have its header on the next line and be flagged (over-block) — or, if the URL is on a later line, the call would not be seen at all (under-block). No shipped template uses continuations; all are single-line.
|
|
41
|
+
- **Non-curl HTTP clients.** A template instructing `wget`, `httpie`, or a `node -e` fetch is not scanned at all.
|
|
42
|
+
- **Correct header, wrong token.** The lint verifies an `Authorization: Bearer` header is *present*; it cannot verify the token *resolves*. That is exactly the `identity-review.md` failure mode (header present, value stale) — caught here by a separate assertion requiring the env-first `${INSTAR_AUTH_TOKEN:-` form in the six known templates, not by the generic rule.
|
|
43
|
+
- **Runtime drift.** A template can be correct at CI time and still 401 at runtime if `$INSTAR_AUTH_TOKEN` is absent from the job environment *and* config.json is stale. The lint cannot see runtime env.
|
|
44
|
+
- **Endpoints that become non-public later.** If a currently-public path (say `/ping`) later requires auth, the lint would keep exempting it. The drift guard only catches *removal* of the path from middleware.ts, not a change in its auth posture.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 3. Level-of-abstraction fit
|
|
49
|
+
|
|
50
|
+
This is a **build-time lint** — the correct layer. The defect is static text in a shipped artifact, fully determinable from source, so it belongs in CI rather than at runtime.
|
|
51
|
+
|
|
52
|
+
Considered and rejected: a runtime check that inspects job bodies before dispatch. That would be the wrong layer — it would pay a cost on every job run to detect a fault that cannot change after the template ships, and it would need blocking authority to be useful, which the signal-vs-authority principle forbids for logic this brittle.
|
|
53
|
+
|
|
54
|
+
The lint extends the existing template-validation family (`default-jobs-valid.test.ts`, `PostUpdateMigrator-templateResolution.test.ts`) rather than running parallel to it. It reads its exemption set *from* the real authority (`middleware.ts`) rather than re-deriving one.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 4. Signal vs authority compliance
|
|
59
|
+
|
|
60
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
61
|
+
|
|
62
|
+
**Does this change hold blocking authority with brittle logic?**
|
|
63
|
+
|
|
64
|
+
- [x] No — this change has no block/allow surface.
|
|
65
|
+
|
|
66
|
+
The lint is a test. Its only power is failing a build. It holds no authority over any agent at runtime: it cannot block a message, gate a job, delay a session, or constrain a decision. The template edits are instructions to an LLM, not control flow. Nothing in this change can refuse an agent action.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 4b. Judgment-point check (Judgment Within Floors standard)
|
|
71
|
+
|
|
72
|
+
**No new static heuristic at a competing-signals decision point.** The question "does this curl carry an Authorization header, and is its endpoint public?" is fully enumerable from source: the public-path set is a finite list read from `authMiddleware()`, and header presence is a textual fact. There are no competing live signals to weigh — no work evidence, liveness, recency, or ownership inputs. This is an invariant check, not a judgment point.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 5. Interactions
|
|
77
|
+
|
|
78
|
+
- **Shadowing:** None. The lint is an independent test file; vitest runs it alongside the existing template tests with no ordering dependency. It does not shadow `default-jobs-valid.test.ts` (which validates job *structure* from `init.ts`, not template *body content*) — the two cover disjoint surfaces.
|
|
79
|
+
- **Double-fire:** None. No runtime component acts on this.
|
|
80
|
+
- **Races:** None. The lint only reads files at test time.
|
|
81
|
+
- **Feedback loops:** One worth naming explicitly. `evolution-proposal-implement.md` is the template that runs the job that produced this change — the fix is self-referential. It is *not* a loop: the template is data read by a scheduled job, and repairing it changes which HTTP calls a future run makes, not whether this change is correct. Verified by executing the fixed commands directly against the live server rather than trusting the job's own behavior.
|
|
82
|
+
- **Migration parity:** `installBuiltinJobs()` (`src/scheduler/InstallBuiltinJobs.ts:127`) unconditionally `writeFileSync`-es every shipped template over the deployed copy, and is called from `PostUpdateMigrator.ts:4046` on every update run. Verified first-hand by reading both call sites. **No separate `PostUpdateMigrator` entry is needed** — and, critically, patching the deployed copies by hand would have been *reverted* at the next update, producing a transient fix indistinguishable from a real one. The durable fix is the source templates, which is what this change edits.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 6. External surfaces
|
|
87
|
+
|
|
88
|
+
- **Other agents on the same machine:** No change. Templates are per-agent state; each agent receives the fixed copy at its next install/update.
|
|
89
|
+
- **Other users of the install base:** Yes, positively — five scheduled jobs that have been silently no-op-ing on every install will begin functioning. Anyone who had these jobs enabled gets working evolution/insight/reflection/identity jobs. Worth calling out in release notes: behavior that appeared "quiet and healthy" will start producing real output.
|
|
90
|
+
- **External systems:** None. All calls are to `localhost`.
|
|
91
|
+
- **Persistent state:** None added. The jobs will now successfully write to existing stores (evolution proposals/actions/learnings, reflection records) that they were previously failing to reach — that is the intended repair, not a new surface.
|
|
92
|
+
- **Timing / runtime conditions:** The fix depends on `$INSTAR_AUTH_TOKEN` being present in the job environment (verified present, 36 chars) with config.json as fallback.
|
|
93
|
+
- **Operator surface (Mobile-Complete Operator Actions):** No operator-facing actions added or touched. This change adds no route, form, approval, or grant.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 6b. Operator-surface quality (Operator-Surface Quality standard)
|
|
98
|
+
|
|
99
|
+
**No operator surface — not applicable.** No dashboard renderer, markup file, approval page, or grant/revoke/secret-drop form is touched. The change is confined to shipped job-template markdown and a test file.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
104
|
+
|
|
105
|
+
**Machine-local BY DESIGN**, with reason: job templates are installed onto each machine's own `.instar/jobs/instar/` by that machine's own `installBuiltinJobs()` run, and the auth token they resolve (`$INSTAR_AUTH_TOKEN` / that machine's `config.json`) is **necessarily** machine-local — each install holds its own `authToken`, and a shared token cannot work cross-machine. Replicating template content or tokens across machines would be actively wrong.
|
|
106
|
+
|
|
107
|
+
Every machine converges on the same fixed template content through the same update path, so there is no divergence risk: this is not "machine-local state that should have been replicated", it is identical shipped content resolved against per-machine credentials.
|
|
108
|
+
|
|
109
|
+
- **User-facing notices:** None emitted by this change. (The *jobs* may now emit notices they previously failed to produce, but each job's own one-voice/topic-routing behavior is unchanged by this fix.)
|
|
110
|
+
- **Durable state on topic transfer:** None held. Nothing strands on a topic move.
|
|
111
|
+
- **Generated URLs:** None generated. All URLs are `localhost` calls made by the job on its own machine, which is correct — they are never shared or sent to a user.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 8. Rollback cost
|
|
116
|
+
|
|
117
|
+
- **Hot-fix release:** Revert the six template files and the test; ship as a patch. Existing agents pick up the reverted content at their next update via the same `installBuiltinJobs()` overwrite path.
|
|
118
|
+
- **Data migration:** None. No schema, column, or persistent state introduced.
|
|
119
|
+
- **Agent state repair:** None required.
|
|
120
|
+
- **User visibility during rollback:** Reverting restores the *previous broken behavior* (jobs silently no-op again). No error state, no crash, no data loss — the pre-fix behavior was quiet failure, so a rollback is quiet too. That asymmetry is itself the argument for why this class of bug survived so long.
|
|
121
|
+
|
|
122
|
+
Rollback risk is minimal: worst case is a return to the status quo ante.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Conclusion
|
|
127
|
+
|
|
128
|
+
This review produced two changes to the work in progress. First, the lint's `AUTH=` detector was rewritten after it falsely accused three healthy templates — a real over-block caught before commit, now pinned by a regression test. Second, the scope grew by two genuine same-class defects found by reading rather than assuming: `identity-review.md`'s config-only token read (broken on this very agent, proven by executing both tokens against the live server) and `reflection-trigger.md`'s quote-mangled JSON body.
|
|
129
|
+
|
|
130
|
+
The change is clear to ship. It has no runtime authority, no persistent state, no operator surface, and a trivial rollback. The verification is first-hand rather than proxy: the lint was proven to report **exactly** the 12 pre-fix violations when run against the original content from `HEAD` and zero after, and the fixed commands were executed against the live server returning real data instead of 401.
|
|
131
|
+
|
|
132
|
+
One honest limitation, stated rather than buried: the lint verifies an auth header is *present*, not that its token *resolves*. Token-resolution correctness is covered for the six known templates by the env-first assertion, but a future template could pass the lint and still 401 with a stale token. Closing that would require a runtime check, which is the wrong layer for the reasons in §3.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Second-pass review (if required)
|
|
137
|
+
|
|
138
|
+
**Reviewer:** not required.
|
|
139
|
+
|
|
140
|
+
Phase 5 triggers on changes touching block/allow decisions on messaging or dispatch, session lifecycle, context/compaction, coherence gates, trust levels, or any sentinel/guard/gate/watchdog. This change touches none of them: it is shipped instruction text plus a CI lint with no runtime surface. The word "gate" appears in the templates' `gate:` frontmatter field, but those lines are **not modified** by this change.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Evidence pointers
|
|
145
|
+
|
|
146
|
+
- **Lint bites (pre-fix):** running the detector against the six original files extracted from `HEAD` reported exactly 12 unauthenticated calls — `evolution-overdue-check` 3, `evolution-proposal-evaluate` 3, `evolution-proposal-implement` 2, `insight-harvest` 3, `reflection-trigger` 1 — matching the independently-derived census. Post-fix: 0.
|
|
147
|
+
- **Live 401 → 200:** `curl` to `/evolution/proposals?status=approved` with no header returns `{"error":"Missing or invalid Authorization header"}`; the fixed template's resolved command returns the real proposal list. Same confirmed for `/evolution/learnings?applied=false` and `/evolution/actions/overdue`.
|
|
148
|
+
- **Stale-config proof:** the 16-char `config.json` token returns `{"error":"Invalid auth token"}`; the 36-char `$INSTAR_AUTH_TOKEN` succeeds — the concrete reason `identity-review.md` needed the env-first form.
|
|
149
|
+
- **Quoting proof:** executing the original `reflection-trigger.md` echo renders `-d '{type:quick}'` (invalid JSON); the fixed line renders `-d '{"type":"quick"}'` with the token name left unexpanded so no secret enters the transcript.
|
|
150
|
+
- **Migration parity:** `InstallBuiltinJobs.ts:127` unconditional overwrite, reached from `PostUpdateMigrator.ts:4046`.
|
|
151
|
+
- **Tests:** `tests/unit/job-template-auth-lint.test.ts` (9 tests) green; template-family tests (`refresh-jobs`, `default-jobs-valid`, `PostUpdateMigrator-templateResolution`) green.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Class-Closure Declaration (display-only mirror)
|
|
156
|
+
|
|
157
|
+
- **`defectClass`** — `novel` is *not* claimed. This is an instance of the existing **unverified-claim / proxy-signal** family already tracked by EVO-004 (ACT-930), EVO-005, and EVO-006 (ACT-985): a signal adjacent to the real terminal state (the `gate:` passing, "the job ran quietly") was treated as evidence of the terminal state (the job actually did its work). Recorded here as `defectClass: proxy-signal-substitution`.
|
|
158
|
+
- **`closure`** — `guard`.
|
|
159
|
+
- **`guardEvidence`** — `{enforcementType: lint, citation: tests/unit/job-template-auth-lint.test.ts#"every shipped template authenticates its non-public API calls", howCaught: the lint scans every shipped template body for curl calls to non-public endpoints lacking an Authorization header and fails the build; run against the pre-fix content it reports exactly the 12 violations that shipped, so this defect could not have reached main with the guard in place}`.
|
|
160
|
+
- **`gap`** — none for the template-text class. The broader "a job that silently no-ops looks identical to a healthy quiet job" class is **not** closed by this change and remains tracked under the EVO-005/EVO-006 family; this fix closes only the specific mechanism (missing auth header in shipped template text).
|