instar 1.3.8 → 1.3.10
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/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +12 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/feedback-factory/processor/similarity.d.ts +24 -0
- package/dist/feedback-factory/processor/similarity.d.ts.map +1 -0
- package/dist/feedback-factory/processor/similarity.js +44 -0
- package/dist/feedback-factory/processor/similarity.js.map +1 -0
- package/dist/scheduler/MentorOnboardingRunner.d.ts +63 -0
- package/dist/scheduler/MentorOnboardingRunner.d.ts.map +1 -0
- package/dist/scheduler/MentorOnboardingRunner.js +60 -0
- package/dist/scheduler/MentorOnboardingRunner.js.map +1 -0
- package/dist/scheduler/MentorOnboardingTick.d.ts +68 -0
- package/dist/scheduler/MentorOnboardingTick.d.ts.map +1 -0
- package/dist/scheduler/MentorOnboardingTick.js +104 -0
- package/dist/scheduler/MentorOnboardingTick.js.map +1 -0
- package/dist/server/AgentServer.d.ts +14 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +79 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +12 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts +3 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +24 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/scripts/feedback-factory/_py_similarity_ref.py +39 -0
- package/scripts/feedback-factory/similarity-corpus.json +25 -0
- package/scripts/feedback-factory/similarity-parity.mjs +72 -0
- package/src/data/builtin-manifest.json +47 -47
- package/src/scaffold/templates/jobs/instar/mentor-onboarding.md +41 -0
- package/upgrades/1.3.10.md +47 -0
- package/upgrades/1.3.9.md +27 -0
- package/upgrades/side-effects/feedback-factory-similarity.md +32 -0
- package/upgrades/side-effects/mentor-onboarding-job.md +186 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- bump: patch -->
|
|
4
|
+
|
|
5
|
+
## What Changed
|
|
6
|
+
|
|
7
|
+
Second increment of the **Feedback Factory Migration** (Dawn → Echo; spec `docs/specs/feedback-factory-migration.md`, approved). Ports the **fuzzy title-similarity primitives** — `_tokenize` + `_jaccard_similarity` — from the reference Python (`the-portal/.claude/scripts/feedback-processor.py`) to TypeScript at `src/feedback-factory/processor/similarity.ts`.
|
|
8
|
+
|
|
9
|
+
These sit on top of the exact-match fingerprinter shipped in the previous increment: the fingerprint groups identically-normalized reports, and Jaccard similarity is the fuzzy layer that decides whether two *differently-worded* titles overlap enough to be the same bug (the input to the clustering merge thresholds). Still an internal building block — **not wired into any route or job yet**, so no behavioral change.
|
|
10
|
+
|
|
11
|
+
## What to Tell Your User
|
|
12
|
+
|
|
13
|
+
- More groundwork for moving the feedback system in-house — the piece that recognizes two differently-worded reports as the same underlying issue.
|
|
14
|
+
- Same discipline as before: I proved my rewrite produces the same similarity scores AND the same group/don't-group decisions as Dawn's original, across 20 title pairs including the tricky non-English-text ones.
|
|
15
|
+
|
|
16
|
+
## Summary of New Capabilities
|
|
17
|
+
|
|
18
|
+
| Capability | How to Use |
|
|
19
|
+
|-----------|-----------|
|
|
20
|
+
| Title-similarity primitives (TS port) | Internal module `src/feedback-factory/processor/similarity.ts` — not yet wired |
|
|
21
|
+
| Similarity parity harness | `node scripts/feedback-factory/similarity-parity.mjs` (local; set `PORTAL_PROCESSOR`) |
|
|
22
|
+
|
|
23
|
+
## Evidence
|
|
24
|
+
|
|
25
|
+
- **Parity vs the real reference Python:** ran `_jaccard_similarity` from the reference processor and the TS port over 20 title pairs (identical / disjoint / partial-overlap / near the 0.35 and 0.55 merge thresholds / empty / punctuation-only / non-ASCII). Result: **20/20 match** — both the raw similarity value and the merge-threshold decision (the math is bit-identical IEEE-754 division; the harness asserts the *decision* too, since that is what actually drives clustering).
|
|
26
|
+
- **ASCII-tokenizer fidelity:** the reference tokenizer is ASCII-only (`[a-z0-9]+` after lowercasing), so the port uses the identical ASCII class — non-English letters and non-ASCII digits are separators in both, verified by the corpus.
|
|
27
|
+
- **CI anchors:** unit tests assert exact ratios (0.6, 0.4) and the threshold ordering, so a tokenizer/division regression fails in CI even without the reference checkout.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Side-Effects Review — feedback-factory similarity primitives (Phase 1, increment 2)
|
|
2
|
+
|
|
3
|
+
**Slug:** `feedback-factory-similarity`
|
|
4
|
+
**Date:** `2026-05-26`
|
|
5
|
+
**Author:** Echo
|
|
6
|
+
**Spec:** `docs/specs/feedback-factory-migration.md` (converged v2, approved by Justin 2026-05-26)
|
|
7
|
+
**Scope:** The fuzzy title-similarity primitives — `_tokenize` (:1389) and `_jaccard_similarity` (:1394) of the reference processor — which sit on top of the exact-match fingerprint (increment 1) and underpin scar (c)'s false-merge guard. The threshold-application/clustering driver and the other scars are subsequent increments.
|
|
8
|
+
|
|
9
|
+
## Summary of the change
|
|
10
|
+
|
|
11
|
+
Ports `_tokenize` + `_jaccard_similarity` from `the-portal/.claude/scripts/feedback-processor.py` to `src/feedback-factory/processor/similarity.ts`. Pure functions, no I/O. **Not wired into any route/job yet** — no behavioral change. Adds the similarity parity harness (`scripts/feedback-factory/similarity-parity.mjs` + `_py_similarity_ref.py` + `similarity-corpus.json`) and Tier-1 unit tests.
|
|
12
|
+
|
|
13
|
+
## Equivalence verification
|
|
14
|
+
|
|
15
|
+
- **20/20 title pairs match the reference Python** — both the raw similarity value (1e-12 tolerance for float-string serialization only; the math is bit-identical IEEE-754 division) AND the threshold decisions (`≥0.55` fixed-merge, `≥0.35` merge, else no-merge), which are what actually drive clustering.
|
|
16
|
+
- The tokenizer is ASCII-only in the reference (`re.findall(r'[a-z0-9]+', text.lower())`), so the JS port uses the identical ASCII class `/[a-z0-9]+/g` (no `u`, no `i`). Non-ASCII letters and Unicode digits are separators in both — verified by corpus entries (café→{caf}, arabic digits dropped, Straße→{stra,e}).
|
|
17
|
+
|
|
18
|
+
## Seven-dimension review
|
|
19
|
+
|
|
20
|
+
1. **Over/under-reach** — Pure deterministic functions, no I/O, no global state, not imported by any runtime path. Cannot affect existing behavior. Risk is equivalence-to-reference, addressed above.
|
|
21
|
+
2. **Level-of-abstraction fit** — Processor-logic layer (`src/feedback-factory/processor/`), alongside the fingerprint port. Correct home.
|
|
22
|
+
3. **Signal vs Authority** — N/A; pure computation. The threshold constants that turn a similarity score into a merge decision live in the (later) clustering driver, where the curated-lifecycle authority also lives.
|
|
23
|
+
4. **Interactions** — None. New isolated module; nothing imports it yet. Parity scripts are LOCAL-only (external reference path via `PORTAL_PROCESSOR`), no CI dependency on the reference checkout.
|
|
24
|
+
5. **Rollback cost** — Trivial: delete the module + tests + scripts. No data, no migration, no wiring.
|
|
25
|
+
6. **Migration parity** — N/A. New internal library code; touches no agent-installed file.
|
|
26
|
+
7. **Failure modes** — (a) Port diverges from reference → caught by the parity harness (20/20) + golden-shaped unit assertions in CI. (b) Float boundary divergence at exactly 0.35/0.55 → mitigated by identical IEEE-754 division + the parity harness asserting threshold DECISIONS, not just values. (c) Reference path absent → harness exits 2 with a clear message (local gate, not CI).
|
|
27
|
+
|
|
28
|
+
## Tests
|
|
29
|
+
|
|
30
|
+
- Tier-1 unit (CI): `tests/unit/feedback-factory/similarity.test.ts` — `tokenize` (lowercase/split/dedup, ASCII-only behavior, empty/punctuation), `jaccardSimilarity` (identical=1.0, disjoint=0.0, empty-guard=0.0, exact ∩/∪ ratios, threshold ordering). 8 tests.
|
|
31
|
+
- Parity (local gate, evidence): `scripts/feedback-factory/similarity-parity.mjs` → **20/20** values + threshold decisions identical to the reference Python.
|
|
32
|
+
- No integration/E2E this increment: not yet wired to a route/job; those tiers attach when the clustering driver + receiver land. Reasoned decision, documented.
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Side-Effects Review — Mentor-onboarding job (§19.4)
|
|
2
|
+
|
|
3
|
+
**Spec:** `docs/specs/FRAMEWORK-ONBOARDING-MENTOR-SPEC.md` (converged 5 iters, approved by Justin)
|
|
4
|
+
**Change:** The live mentor loop, shipped DORMANT. Pure tick core (`runMentorTick`), thin runner
|
|
5
|
+
(`MentorOnboardingRunner`), routes (`GET /mentor/status`, `POST /mentor/tick`), AgentServer wiring,
|
|
6
|
+
`mentor.*` config defaults, CapabilityIndex entry, and a built-in job template (`enabled:false`).
|
|
7
|
+
**Files:** `src/scheduler/MentorOnboardingTick.ts`, `src/scheduler/MentorOnboardingRunner.ts`,
|
|
8
|
+
`src/server/routes.ts`, `src/server/AgentServer.ts`, `src/server/CapabilityIndex.ts`,
|
|
9
|
+
`src/config/ConfigDefaults.ts`, `src/scaffold/templates/jobs/instar/mentor-onboarding.md`,
|
|
10
|
+
`tests/unit/MentorOnboardingTick.test.ts`, `tests/unit/MentorOnboardingRunner.test.ts`,
|
|
11
|
+
`tests/integration/mentor-routes.test.ts`, `tests/e2e/mentor-onboarding-lifecycle.test.ts`,
|
|
12
|
+
`upgrades/NEXT.md`.
|
|
13
|
+
|
|
14
|
+
## Principle check (Phase 1)
|
|
15
|
+
|
|
16
|
+
Does this involve a decision point that gates info flow / blocks actions / constrains behavior?
|
|
17
|
+
**Yes — and it's built signal-only.** The tick *decides* whether to act (canary→budget→safe-window),
|
|
18
|
+
spawns a constrained sub-agent, and writes findings. But none of it has authority over the user's
|
|
19
|
+
world: today it only writes to the read-only ledger (no mentee-delivery path is wired yet — see
|
|
20
|
+
Live-promotion blockers). It ships dormant (`mentor.enabled=false`, `mode:'off'`), so the production
|
|
21
|
+
default is `POST /mentor/tick → {ran:false, reason:'disabled'}` — no spawn, no spend, no contact.
|
|
22
|
+
|
|
23
|
+
## The seven questions
|
|
24
|
+
|
|
25
|
+
1. **Over-block.** The safe-window check is conservative: `isMenteeBusy()` treats ANY running
|
|
26
|
+
session as "busy," so the mentor errs toward not-interrupting (correct bias). The budget cap and
|
|
27
|
+
min-interval floor likewise skip rather than act. No legitimate *required* action is blocked —
|
|
28
|
+
the mentor is purely additive.
|
|
29
|
+
|
|
30
|
+
2. **Under-block.** The Stage-B *deep* log-forensics (assembling the mentee's rollouts/diffs and
|
|
31
|
+
LLM-classifying them) is not yet wired — `runStageBForensics` returns `[]` today, so the loop's
|
|
32
|
+
real signal is the Stage-A leak detector (§4.3) + the funnel. This is a tracked follow-on
|
|
33
|
+
(<!-- tracked: topic-13435 -->), not a silent gap: the funnel logs every run, so a loop producing
|
|
34
|
+
no findings is visible, not hidden. `getSurface` returns an empty history until the conversation
|
|
35
|
+
source is wired (same tracked follow-on) — meaning until then Stage A has thin context, which the
|
|
36
|
+
off-by-default + dry-run-first rollout is designed to surface before live.
|
|
37
|
+
|
|
38
|
+
3. **Level-of-abstraction fit.** The orchestration is a PURE function (`runMentorTick`) with all
|
|
39
|
+
side-effects injected — the structural guarantees (canary-first, fail-closed budget, safe-window)
|
|
40
|
+
are in code, not the job prompt. The runner is thin glue; the job template is a thin timer that
|
|
41
|
+
pokes `POST /mentor/tick`. Spawn enforcement is delegated to the right layer (SessionManager
|
|
42
|
+
`--allowedTools`). This is the correct decomposition.
|
|
43
|
+
|
|
44
|
+
4. **Signal vs authority.** Compliant. The tick produces signal (ledger findings) only — it never
|
|
45
|
+
merges code, advances graduation, or gates another component, and no mentee-delivery path is wired
|
|
46
|
+
yet. Authority stays with the human (§6/§8). The empty Stage-A tool grant is CLI-enforced, not
|
|
47
|
+
self-policed.
|
|
48
|
+
|
|
49
|
+
5. **Interactions.** The runner reuses `ledger.captureRun` (funnel logs every run), `MentorStageA`
|
|
50
|
+
(empty grant + leak detector), and SessionManager.spawnSession. Min-interval + per-day counters
|
|
51
|
+
live on AgentServer and advance only via the `onTickRan` hook (so skipped ticks don't consume the
|
|
52
|
+
budget). A busy-session check prevents the mentor from interrupting other work. No shadowing of
|
|
53
|
+
existing sentinels; the mentor never kills/restarts anything.
|
|
54
|
+
|
|
55
|
+
6. **External surfaces.** Two new routes behind Bearer auth (e2e: 401 without). A built-in job that
|
|
56
|
+
ships `enabled:false` (e2e asserts this). `mentor.*` config flows through the canonical
|
|
57
|
+
ConfigDefaults registry → existing agents get it via `applyDefaults` on migration (no bespoke
|
|
58
|
+
migrator block; matches `sessionReaper`/`topicIntent`). CapabilityIndex entry surfaces the routes.
|
|
59
|
+
No mentee-delivery path exists yet (Live-promotion blockers), so even `live` mode only observes.
|
|
60
|
+
|
|
61
|
+
7. **Rollback cost.** Low. Dormant by default; revert removes the routes/job/config. No data
|
|
62
|
+
migration (the ledger/funnel tables are §19.1/19.2). The job auto-uninstalls when the template is
|
|
63
|
+
removed (installBuiltinJobs prunes slugs no longer in the templates dir).
|
|
64
|
+
|
|
65
|
+
## Phase 5 — second-pass (REQUIRED here; deferred from §19.3)
|
|
66
|
+
|
|
67
|
+
This change spawns sub-agents and contains gating logic, so a dedicated reviewer audited it
|
|
68
|
+
independently. Their verdict is appended below (concur, 0 blocking concerns).
|
|
69
|
+
|
|
70
|
+
## Live-promotion blockers (must close before `mentor.mode` → `live`) <!-- tracked: topic-13435 -->
|
|
71
|
+
|
|
72
|
+
The second-pass surfaced three items that are unreachable while dormant but MUST be closed before the
|
|
73
|
+
graduated-rollout flips the mentor to `live`. They are explicitly NOT ship-blockers for this dormant
|
|
74
|
+
change; they are the gate for activation:
|
|
75
|
+
|
|
76
|
+
1. **No mentee-delivery path is wired.** The tick surfaces `stageAMessage` but nothing forwards it to
|
|
77
|
+
the mentee. The persist-only delivery (§6) must be built + tested before `live` can actually
|
|
78
|
+
contact anyone. (Code comments + Q1/Q4/Q6 above corrected to say this.)
|
|
79
|
+
2. **Spawn poll robustness + real budget accounting.** `spawnStageA` polls 90×2s then captures the
|
|
80
|
+
pane; on poll-exhaustion it must kill the session and capture a `stage-a-timeout` finding rather
|
|
81
|
+
than read a partial transcript and orphan the tmux session. And `dailySpendCapUsd` is presently a
|
|
82
|
+
run-count cap (`maxRoundsPerDay`) — wire real cost accounting or rename the knob before live.
|
|
83
|
+
3. **Async tick.** The 180s poll runs on the `POST /mentor/tick` request thread; before live, return
|
|
84
|
+
`202` + poll completion via `/mentor/status` (avoid the gate-latency-vs-client-timeout failure).
|
|
85
|
+
|
|
86
|
+
All three are gated behind `mentor.enabled=false` today and covered by the off→dry-run→live promotion
|
|
87
|
+
discipline (each step needs Justin's sign-off), so the dormant ship is safe.
|
|
88
|
+
|
|
89
|
+
## Testing
|
|
90
|
+
|
|
91
|
+
- Tier 1 (unit): 8 `runMentorTick` (gate order: canary→budget→safe-window→Stage A→leak→Stage B→
|
|
92
|
+
capture; fail-closed budget before contact; leak capture; spawn-failure self-report) + 6 runner
|
|
93
|
+
(dormant short-circuit, busy/min-interval unsafe, status).
|
|
94
|
+
- Tier 2 (integration): 4 — `/mentor/status` 503/200; `/mentor/tick` disabled-by-default + runs-when-enabled.
|
|
95
|
+
- Tier 3 (e2e "alive"): 5 — real AgentServer boot, status 200-not-503 + dormant, tick disabled on the
|
|
96
|
+
production path, /capabilities surfaces it, 401 without auth, job template ships `enabled:false`.
|
|
97
|
+
- Affected push-config suite vs canonical main: 3431 + 300 capability tests green, no regressions.
|
|
98
|
+
|
|
99
|
+
## Reviewer second-pass
|
|
100
|
+
|
|
101
|
+
**Concur with the review — with three non-blocking concerns to track.**
|
|
102
|
+
|
|
103
|
+
Independent audit of `MentorOnboardingTick.ts`, `MentorOnboardingRunner.ts`, the `buildMentorRunner`
|
|
104
|
+
wiring in `AgentServer.ts`, the `/mentor/status` + `/mentor/tick` handlers in `routes.ts`, and
|
|
105
|
+
`MentorStageA.ts`/`ConfigDefaults.ts`. The core safety properties hold:
|
|
106
|
+
|
|
107
|
+
- **Signal vs authority (Q1): clean.** The tick only writes to the read-only ledger via `capture`
|
|
108
|
+
and returns a `stageAMessage` string. It never kills/gates/blocks any component. `isMenteeBusy`
|
|
109
|
+
only makes the mentor skip *itself*. No brittle blocking authority anywhere.
|
|
110
|
+
- **Fail-closed / canary-first (Q2): real.** Order in `runMentorTick` is canary → budget →
|
|
111
|
+
safe-window → Stage A → leak → Stage B → capture. The budget gate (line 108) precedes the only
|
|
112
|
+
spawn (line 116). Every skip returns early before `capture`, so there is no partial
|
|
113
|
+
Stage-A-without-capture. Canary failure self-reports and halts.
|
|
114
|
+
- **Dormancy (Q3): genuinely inert.** `DEFAULT_MENTOR_CONFIG` and `ConfigDefaults.mentor` both ship
|
|
115
|
+
`enabled:false`/`mode:'off'`. `tick()` short-circuits to `{ran:false,reason:'disabled'}` BEFORE
|
|
116
|
+
touching any service. With defaults, `POST /mentor/tick` cannot spawn or spend. Verified by the
|
|
117
|
+
`enabled:true + mode:'off'` unit test.
|
|
118
|
+
- **Budget/min-interval counters (Q5): no double-count, no bypass.** `onTickRan` fires only when
|
|
119
|
+
`result.ran` (runner line 95); all five skip reasons return `ran:false` → counters do not advance
|
|
120
|
+
→ skipped ticks consume no budget.
|
|
121
|
+
- **Two-hats (Q6): structurally enforced.** `STAGE_A_ALLOWED_TOOLS = []` is passed as
|
|
122
|
+
`allowedTools: [...STAGE_A_ALLOWED_TOOLS]`; the leak detector runs on the transcript and a leak is
|
|
123
|
+
captured as a finding. Empty-grant is CLI-enforced, not prompt-policed.
|
|
124
|
+
|
|
125
|
+
**Concern 1 (non-blocking) — the artifact overstates live-mode delivery; there is in fact NO
|
|
126
|
+
delivery path wired at all.** The artifact (lines 20, 44, 58) and the code comments
|
|
127
|
+
(`MentorOnboardingTick.ts:155-156`, `155` "delivered only in `live` mode by the runner") describe a
|
|
128
|
+
"persist-only path" that messages the mentee in `live` mode. That path does not exist: `tick()` in
|
|
129
|
+
`MentorOnboardingRunner.ts` returns `stageAMessage` and the route handler in `routes.ts:4275-4276`
|
|
130
|
+
simply JSON-returns the result; nothing ever forwards the message to the mentee (no
|
|
131
|
+
`messageRouter`/`relay`/`inject` call in the runner, tick, or route — grep-confirmed). This is
|
|
132
|
+
actually SAFER than advertised (live mode cannot contact anyone today — it is functionally
|
|
133
|
+
dry-run-plus-counter), so it is not a blocking safety issue. But it makes the "Signal vs authority /
|
|
134
|
+
live mode would message the mentee" framing inaccurate and means the highest-risk surface (actual
|
|
135
|
+
outbound contact) is entirely unbuilt and *untested*. The "delivered via the persist-only path"
|
|
136
|
+
comments should be corrected to "delivery is a tracked follow-on; live mode currently runs the loop
|
|
137
|
+
without contacting the mentee," and the live-delivery work tracked alongside the §19.4 follow-on
|
|
138
|
+
(topic-13435). Fix: amend the artifact + the two `MentorOnboardingTick.ts` comments; track delivery
|
|
139
|
+
as not-yet-built.
|
|
140
|
+
|
|
141
|
+
**Concern 2 (non-blocking) — the spawn poll can leak/orphan a Stage-A session, and the budget gate
|
|
142
|
+
counts ticks, not dollars.** In `buildMentorRunner` (`AgentServer.ts:675-680`) the spawn polls
|
|
143
|
+
90×2s = 180s for the session to disappear from `listRunningSessions()`. `maxDurationMinutes:5` does
|
|
144
|
+
NOT bound the spawn within the poll window: the timeout is enforced lazily by the monitor tick with
|
|
145
|
+
a 20%-or-60-min buffer (`SessionManager.ts:598-602`), so a wedged Stage-A session is only reaped at
|
|
146
|
+
~6 min — after the poll has already given up at 3 min. When the poll falls through, the code
|
|
147
|
+
captures whatever is on the pane and returns it as the "transcript" of an *unfinished* session
|
|
148
|
+
(garbage-in to the leak detector), while the tmux session keeps running until the monitor reaps it.
|
|
149
|
+
This is bounded (it will be reaped) and dormant today, but at live cadence (10-min floor, 24/day) a
|
|
150
|
+
chronically-slow mentee could overlap a still-running prior Stage-A against the next tick's
|
|
151
|
+
`isMenteeBusy` check (which would then correctly skip). Separately, `dailySpendCapUsd` (0.5) is
|
|
152
|
+
declared in config and the artifact calls the gate a "daily spend cap," but `budgetOk` only checks
|
|
153
|
+
`mentorRunsToday < maxRoundsPerDay` — it never accounts dollars. The cap is a *run-count* cap, not a
|
|
154
|
+
spend cap. Fix before live promotion: (a) treat poll-exhaustion distinctly from
|
|
155
|
+
session-completed — if the session is still running at 90 iterations, kill it explicitly and capture
|
|
156
|
+
a `stage-a-timeout` finding rather than returning a partial pane as a clean transcript; (b) either
|
|
157
|
+
wire `dailySpendCapUsd` to a real cost accounting (TokenLedger) or rename it / drop it from the
|
|
158
|
+
config + artifact so it isn't mistaken for a spend ceiling.
|
|
159
|
+
|
|
160
|
+
**Concern 3 (non-blocking) — the 180s synchronous poll runs on the request thread of
|
|
161
|
+
`POST /mentor/tick`.** `tick()` awaits `spawnStageA`, which awaits the 90×2s loop, so a live tick
|
|
162
|
+
holds the HTTP request open up to ~3 min. The built-in job is the only caller today and can tolerate
|
|
163
|
+
it, but any client (or the job runner's own HTTP timeout) hitting `/mentor/tick` will block or 408
|
|
164
|
+
mid-tick. This is the gate-latency-vs-client-timeout failure mode the team has hit before. The tick
|
|
165
|
+
core is non-blocking; the issue is purely the synchronous-spawn wiring in `buildMentorRunner`. Not a
|
|
166
|
+
dormant-mode risk. Fix before live: make `/mentor/tick` kick the tick and return `202 accepted`
|
|
167
|
+
(fire-and-poll via `/mentor/status`), or bound the request with an explicit handler timeout that is
|
|
168
|
+
distinct from spawn completion.
|
|
169
|
+
|
|
170
|
+
None of the three blocks dormant ship: with `enabled:false` the tick never runs, so the spawn/poll,
|
|
171
|
+
the (absent) delivery, and the spend-cap mislabel are all unreachable in production today. They are
|
|
172
|
+
promotion-blockers for `live`, not ship-blockers for this dormant change. They must be resolved (and
|
|
173
|
+
the delivery path actually built + tested) before `mentor.mode` is flipped to `live` — recommend the
|
|
174
|
+
graduated-rollout track gate `dry-run → live` on closing all three.
|
|
175
|
+
|
|
176
|
+
BLOCKING CONCERNS: 0
|
|
177
|
+
|
|
178
|
+
## Post-push CI fixes
|
|
179
|
+
|
|
180
|
+
Two CI checks caught issues, both fixed:
|
|
181
|
+
- `route-completeness.test.ts` requires every `catch (err)` to pair with an `err instanceof Error`
|
|
182
|
+
check; the `/mentor/tick` handler used `(err as Error).message`. Switched to
|
|
183
|
+
`err instanceof Error ? err.message : String(err)`.
|
|
184
|
+
- `Docs Coverage` enforces a per-type floor; the new `mentor-onboarding` job dropped the `job`
|
|
185
|
+
floor (85%) since it was undocumented. Documented it in `reference/default-jobs.md` +
|
|
186
|
+
`architecture/the-living-system.md` (job coverage → 86%).
|