instar 0.28.96 → 0.28.98
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/types.d.ts +30 -4
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/scheduler/AgentMdJobLoader.d.ts +104 -0
- package/dist/scheduler/AgentMdJobLoader.d.ts.map +1 -0
- package/dist/scheduler/AgentMdJobLoader.js +805 -0
- package/dist/scheduler/AgentMdJobLoader.js.map +1 -0
- package/dist/scheduler/JobLoader.d.ts +4 -0
- package/dist/scheduler/JobLoader.d.ts.map +1 -1
- package/dist/scheduler/JobLoader.js +59 -3
- package/dist/scheduler/JobLoader.js.map +1 -1
- package/dist/scheduler/JobScheduler.d.ts.map +1 -1
- package/dist/scheduler/JobScheduler.js +31 -1
- package/dist/scheduler/JobScheduler.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +195 -3
- package/dist/server/routes.js.map +1 -1
- package/dist/tasks/task-flow-types.d.ts +1 -1
- package/package.json +3 -1
- package/scripts/upgrade-guide-validator.mjs +3 -4
- package/src/data/builtin-manifest.json +48 -48
- package/upgrades/0.28.97.md +146 -0
- package/upgrades/0.28.98.md +47 -0
- package/upgrades/side-effects/NEXT.md +68 -61
- package/upgrades/side-effects/jobs-as-agentmd-phase-1a.md +153 -0
- package/upgrades/side-effects/project-scope-phase1-7-skill-surface.md +175 -0
|
@@ -1,61 +1,68 @@
|
|
|
1
|
-
# Side-
|
|
2
|
-
|
|
3
|
-
**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
1
|
+
# Side-effects review — validator: tighten FIX_PATTERNS to reduce false positives
|
|
2
|
+
|
|
3
|
+
**Scope**: Three `FIX_PATTERNS` in `upgrade-guide-validator.mjs` were matching
|
|
4
|
+
technical prose that was not claiming a bug fix, causing the publish CI to
|
|
5
|
+
block with a false "no Evidence section" error on the Phase 1b connect-the-dots
|
|
6
|
+
release (NEXT.md v0.28.96).
|
|
7
|
+
|
|
8
|
+
**Root cause**: The NEXT.md for the Phase 1b connect-the-dots release used
|
|
9
|
+
three words in technical contexts that the validator treated as bug-fix claims:
|
|
10
|
+
- `broken` — "while a child is broken" (describes a runtime state, not a defect
|
|
11
|
+
we fixed)
|
|
12
|
+
- `crashed` — "a crashed runner" (adjective, describes a process that exited
|
|
13
|
+
non-zero, not a crash we fixed)
|
|
14
|
+
- `resolve` — "items resolve" and `resolve-conflict` (validation-check language
|
|
15
|
+
and API action-verb name, not "I fixed an issue")
|
|
16
|
+
|
|
17
|
+
**Files touched**:
|
|
18
|
+
- `scripts/upgrade-guide-validator.mjs` — three pattern changes:
|
|
19
|
+
1. `/\bcrashed\b/i` removed. `crashes?` and `crashing` (verb forms) are kept.
|
|
20
|
+
2. `/\bbroken\b/i` tightened to `/\b(?:was|were)\s+broken\b/i` — only the
|
|
21
|
+
predicate form ("X was broken") triggers; attributive uses ("a broken
|
|
22
|
+
pipeline") and present-state descriptions ("is broken") do not.
|
|
23
|
+
3. `/\bresolves?\b/i` tightened to `/\bresolves?\s+(?:an?\b|the\b)/i` — only
|
|
24
|
+
"resolves a/an/the <defect>" triggers; bare "resolve" as a technical verb
|
|
25
|
+
("items resolve") and hyphenated action verbs ("resolve-conflict") do not.
|
|
26
|
+
- `tests/unit/upgrade-guide-evidence.test.ts` — three regression tests added,
|
|
27
|
+
one per false-positive pattern.
|
|
28
|
+
|
|
29
|
+
**Under-block**: Three narrow edge cases are no longer caught:
|
|
30
|
+
|
|
31
|
+
1. `crashed` standalone past tense: "the server crashed when X" without also
|
|
32
|
+
containing "fix" or another trigger. In practice any guide describing a crash
|
|
33
|
+
fix would also say "fixed a crash" or "fixes the crash" — caught by the
|
|
34
|
+
existing `\bfix(es|ed|ing)?\b` pattern. Removal of `crashed` alone is
|
|
35
|
+
low-risk.
|
|
36
|
+
|
|
37
|
+
2. `broken` as attributive adjective or present state: "fixed the broken poller"
|
|
38
|
+
still triggers because "fixed" catches it. "X is broken (and we fixed it)"
|
|
39
|
+
would also need "fixed" or another trigger — acceptable.
|
|
40
|
+
|
|
41
|
+
3. `resolves` without article: "Resolves race condition" (unusual English, almost
|
|
42
|
+
always "Resolves the/a race condition"). The tightened pattern retains all
|
|
43
|
+
natural phrasing.
|
|
44
|
+
|
|
45
|
+
**Over-block**: None introduced. The change makes the validator more permissive.
|
|
46
|
+
Historical guide warnings decrease slightly.
|
|
47
|
+
|
|
48
|
+
**Level-of-abstraction fit**: `claimsFix` is a pure heuristic signal. The gate
|
|
49
|
+
(`validateGuideContent` requiring Evidence) is the authority. These changes stay
|
|
50
|
+
entirely within the signal layer. Evidence requirement, Evidence validation, and
|
|
51
|
+
all structural checks are unchanged.
|
|
52
|
+
|
|
53
|
+
**Signal vs authority**: Compliant. Tightened patterns are still signals; blocking
|
|
54
|
+
authority remains in the Evidence-section check. No new blocking authority.
|
|
55
|
+
|
|
56
|
+
**Interactions**: `claimsFix` is called only from `validateGuideContent`, called
|
|
57
|
+
by `check-upgrade-guide.js` (CI publish gate) and the test suite. No other
|
|
58
|
+
callers. Historical guide warnings decrease slightly — correct noise reduction.
|
|
59
|
+
|
|
60
|
+
**External surfaces**: None. `claimsFix`/`evidenceIssues` are not part of the
|
|
61
|
+
public npm package surface. No API, config, or CLI change.
|
|
62
|
+
|
|
63
|
+
**Rollback cost**: Trivial. 7 lines changed in validator, 18 lines added to test.
|
|
64
|
+
Revert brings back the over-broad patterns.
|
|
65
|
+
|
|
66
|
+
**Tests**: 24/24 unit tests pass in `upgrade-guide-evidence.test.ts` (3 new +
|
|
67
|
+
21 prior). 1294/1294 tests pass across all three upgrade-guide test files.
|
|
68
|
+
`check-upgrade-guide.js` passes against the previously-blocking NEXT.md.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Side-Effects Review — jobs-as-agentmd Phase 1a
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `jobs-as-agentmd-phase-1a`
|
|
4
|
+
**Date:** `2026-05-12`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** _appended below at completion_
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Phase 1a of the INSTAR-JOBS-AS-AGENTMD spec lands the **loader-side only** support for the new agentmd format. The scheduler can now read per-slug manifests at `.instar/jobs/schedule/<slug>.json`, validate them, resolve and parse a markdown body at `.instar/jobs/<origin>/<slug>.md`, and surface fully-populated `JobDefinition` records into memory. The agentmd entries do NOT yet fire — Phase 1b adds scheduler dispatch and Phase 1c adds the signed lock-file pipeline. Existing `execute.type: "prompt" | "skill" | "script"` entries are unchanged. Files touched:
|
|
11
|
+
|
|
12
|
+
- `src/core/types.ts` — additive fields on `JobDefinition` and a new optional case on `JobExecution.type`. New `AgentMdExecute` interface.
|
|
13
|
+
- `src/scheduler/AgentMdJobLoader.ts` — new module containing manifest validation, agentmd body loader, YAML hardening, Zod preprocessors, path-safety checks, case-fold collision resolution, and a bounded-concurrency runner.
|
|
14
|
+
- `src/scheduler/JobLoader.ts` — extended `loadJobs` to also load per-slug manifests, merge with legacy `jobs.json` (per-slug wins on collision), and re-export `LoadProblem`.
|
|
15
|
+
- `src/scheduler/JobScheduler.ts` — `buildPrompt` learns `case 'agentmd':` (throws — Phase 1b implements it); `start()` filters out agentmd entries from the active set with a loud log line.
|
|
16
|
+
- `tests/unit/scheduler/JobLoader.agentmd.test.ts` — 68 new tests.
|
|
17
|
+
- `tests/unit/scheduler/agentmd-helpers.ts` — synthetic-agent fixture helper.
|
|
18
|
+
- `package.json` — adds `js-yaml ≥ 4` and `@types/js-yaml`. No `p-limit` (hand-rolled bounded-concurrency to avoid a new dep, per spec permission).
|
|
19
|
+
- `upgrades/NEXT.md` — release notes with Evidence section.
|
|
20
|
+
|
|
21
|
+
The decision points this change interacts with:
|
|
22
|
+
|
|
23
|
+
## Decision-point inventory
|
|
24
|
+
|
|
25
|
+
- **Job validation** (`JobLoader.validateJob`) — pass-through (unchanged). New manifest validator (`AgentMdJobLoader.validateManifest`) covers the per-slug shape.
|
|
26
|
+
- **YAML parsing surface** — added. `js-yaml` FAILSAFE_SCHEMA + parsed-tree anchor rejection. Hard-invariant boundary validation.
|
|
27
|
+
- **Path safety on filesystem reads** — added. `realpath` + `lstat` + slug regex. Hard-invariant structural checks.
|
|
28
|
+
- **Tool-allowlist gate** (existing, owned by Phase 1b/1c) — pass-through. Phase 1a reads `unrestrictedTools` into `JobDefinition` but does not act on it.
|
|
29
|
+
- **Grounding-audit gate** (`JobLoader.auditGrounding`) — pass-through. Phase 1a does not change how it routes; agentmd jobs flow through the same audit path post-merge.
|
|
30
|
+
- **Scheduler dispatch** (`JobScheduler.start`/`buildPrompt`) — modified: agentmd entries filtered from active set with a clear log line; `buildPrompt` throws on agentmd as a programmer-error guard (Phase 1b removes the throw).
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 1. Over-block
|
|
35
|
+
|
|
36
|
+
**What legitimate inputs does this change reject that it shouldn't?**
|
|
37
|
+
|
|
38
|
+
The change adds three rejection surfaces — all of them documented and tested:
|
|
39
|
+
|
|
40
|
+
- **YAML anchor rejection.** Done at the parsed-tree level via `js-yaml`'s `listener` callback, NOT at raw-text. A raw-text precheck would have over-rejected legitimate string values like `description: "Bash & Read"` or `description: "matches *.md files"`. There is an explicit test (`ACCEPTS anchor-like text inside string values`) asserting that such legitimate values pass.
|
|
41
|
+
- **Frontmatter key whitelist.** Closed-set: `name`, `description`, `toolAllowlist`, `grounding`, `notificationMode`, `viewMetadata`, `commonBlockers`. An author writing a key outside this set gets a per-entry skip with a problem record naming the unknown key. Phase 1a starter set; the spec's later phases extend it. The over-block is bounded: adding a new key is a code change, not silent.
|
|
42
|
+
- **Slug regex.** ASCII-only `^[a-zA-Z0-9_-]{1,100}$`. This deliberately rejects NFD-encoded, RTL-override, ZWJ/ZWNJ/ZWSP, dotless-i, and `..`/`/`/NUL inputs. Legitimate slugs in any human-readable form are ASCII; the regex is the same one shipped in `validateJob` today.
|
|
43
|
+
|
|
44
|
+
For each surface, per-entry skip is the response (the scheduler keeps running with the surviving jobs) — not refuse-to-boot.
|
|
45
|
+
|
|
46
|
+
## 2. Under-block
|
|
47
|
+
|
|
48
|
+
**What failure modes does this still miss?**
|
|
49
|
+
|
|
50
|
+
- **Manifest "value" field for agentmd.** The validator rejects `execute.value` when `execute.type === "agentmd"`. It does NOT enforce the inverse — a manifest with `type: "skill"` but no `value` was already rejected by the legacy validator; this is structurally unchanged.
|
|
51
|
+
- **Frontmatter `commonBlockers` deep-validation.** Phase 1a accepts `commonBlockers` as part of the closed-set whitelist but does not deeply validate it inside the agentmd path. The legacy `validateCommonBlockers` in `JobLoader.ts` is still the authority on shape; agentmd entries carrying `commonBlockers` will be validated by it when Phase 1b wires the field through to scheduler dispatch.
|
|
52
|
+
- **Hash verification against lock-file.** This is the explicit responsibility of Phase 1c. Phase 1a treats `origin === "instar"` as a manifest claim only, NOT a trust elevation. Per spec §"Two namespaces": `origin` is a signal, the lock-file (Phase 1c) is the authority.
|
|
53
|
+
- **Per-process file descriptor exhaustion.** Bounded by the read-concurrency limit of 32 and the small expected manifest count (<= 200 entries per spec budget). Tested up to 10 concurrent reads.
|
|
54
|
+
|
|
55
|
+
These misses are bounded-by-design and pinned to subsequent phases.
|
|
56
|
+
|
|
57
|
+
## 3. Level-of-abstraction fit
|
|
58
|
+
|
|
59
|
+
**Is this at the right layer?**
|
|
60
|
+
|
|
61
|
+
The change introduces structural, deterministic validation gates at the I/O boundary:
|
|
62
|
+
|
|
63
|
+
- **Zod schema validation** of typed frontmatter fields → hard-invariant type validation, executed once per file at load. Not judgment, not context-dependent.
|
|
64
|
+
- **YAML parser hardening** (FAILSAFE_SCHEMA, anchor-walk, size caps) → structural rejection of malformed input. Same shape as the JSON parsing in `validateJob` today.
|
|
65
|
+
- **Path safety** (`realpath`/`lstat`/regex) → deterministic filesystem-shape checks. Same shape as existing path safety in SafeFsExecutor.
|
|
66
|
+
|
|
67
|
+
None of these are smart-gate-replacements. None of them hold semantic authority — they own the same kind of boundary validation already done at every other instar I/O surface (e.g. `validateJob`, manifest validation in `MachineRegistry`, etc.). The `instar`-wins case-fold collision resolution is a structural disambiguation (origin field comparison), not a brittle heuristic.
|
|
68
|
+
|
|
69
|
+
## 4. Signal vs authority compliance
|
|
70
|
+
|
|
71
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
72
|
+
|
|
73
|
+
**Does this change hold blocking authority with brittle logic?**
|
|
74
|
+
|
|
75
|
+
- [x] No — this change has no block/allow surface that requires conversational reasoning. The validation gates added here are exactly the kinds the doc enumerates as legitimate:
|
|
76
|
+
- **Boundary type validation (Zod):** hard-invariant.
|
|
77
|
+
- **Structural rejection of malformed/unsafe input (YAML parser hardening):** hard-invariant.
|
|
78
|
+
- **Path safety on filesystem reads:** deterministic checks.
|
|
79
|
+
- **Case-fold collision resolution by origin:** structural disambiguation rule, not a brittle blocker. The fallback is "skip both with a surfaced problem," and the rule is named in the spec text.
|
|
80
|
+
|
|
81
|
+
The change introduces **no new gate that consumes user message intent or conversational context.** The per-entry skip pattern is the existing pattern in `JobLoader.ts`. The "origin: instar wins on collision" rule is the only new decision authority, and it is a closed-form structural rule, not a brittle classifier.
|
|
82
|
+
|
|
83
|
+
## 5. Interactions
|
|
84
|
+
|
|
85
|
+
**Does this interact with existing checks, recovery paths, or infrastructure?**
|
|
86
|
+
|
|
87
|
+
- **Shadowing.** Per-slug manifests shadow legacy `jobs.json` entries with the same slug. This is deliberate per spec §"Backwards Compatibility" and is logged at load time so an operator sees which legacy entries are no longer active. Test: `per-slug manifest shadows legacy jobs.json entry of the same slug`.
|
|
88
|
+
- **Double-fire.** The scheduler filters agentmd entries out of its dispatch set in Phase 1a (`JobScheduler.start` filter). `buildPrompt` throws on agentmd as a defense-in-depth guard. Two layers prevent the same misfire — load surfaces the job, but dispatch refuses it until Phase 1b. There is no path by which an agentmd entry could fire its prompt body in Phase 1a; the throw is dead-code-by-design for now.
|
|
89
|
+
- **Races.** All filesystem I/O is sync (matching the existing `loadJobs` sync API). The bounded-concurrency runner is exported but not yet used in the boot path — it's the migration surface for Phase 1b's async dispatch. No shared mutable state is introduced.
|
|
90
|
+
- **Feedback loops.** None. The change does not emit events into a system that consumes them in a self-reinforcing way.
|
|
91
|
+
|
|
92
|
+
The interaction with `JobScheduler.buildPrompt` deserves a closer look. Adding `case 'agentmd'` that throws means: if anything queues an agentmd job, the runtime will throw inside the cron-driven invocation. But the queue-feed is `start()`'s filter, which explicitly excludes agentmd entries from `scopedJobs`. Tested + verified — the throw is unreachable in Phase 1a runtime. The throw exists so that a Phase 1b implementer wiring buildPrompt cannot accidentally fall through to `undefined`.
|
|
93
|
+
|
|
94
|
+
## 6. External surfaces
|
|
95
|
+
|
|
96
|
+
**Does this change anything visible outside the immediate code path?**
|
|
97
|
+
|
|
98
|
+
- **Other agents on the same machine?** No. The change is internal to one agent's `.instar/jobs/` tree.
|
|
99
|
+
- **Other users of the install base?** No behavioral change for any pre-spec agent (jobs.json-only is unchanged).
|
|
100
|
+
- **External systems?** None — no network calls added.
|
|
101
|
+
- **Persistent state?** The change reads `.instar/jobs/schedule/<slug>.json` and `.instar/jobs/<origin>/<slug>.md` if those paths exist. It does NOT write or migrate any state. Migration is Phase 3.
|
|
102
|
+
- **Timing or runtime conditions?** Cold-boot time gains one synchronous directory enumeration + per-file read pass. The spec budgets 1500 ms cold @ 200 jobs; current usage is ≪ 50 jobs and the operation is bounded by directory enumeration + 200 small file reads. Well inside budget.
|
|
103
|
+
- **Per-machine commit storms?** The change does not touch the git-sync auto-commit path. Schedule directory files are read-only inputs.
|
|
104
|
+
|
|
105
|
+
## 7. Rollback cost
|
|
106
|
+
|
|
107
|
+
**If this turns out wrong in production, what's the back-out?**
|
|
108
|
+
|
|
109
|
+
Pure additive code change with no persistent state migration. Rollback options:
|
|
110
|
+
|
|
111
|
+
- **Hot-fix revert:** revert the commit. Per-slug manifests on disk simply stop being read; legacy `jobs.json` continues to drive the scheduler. No user-visible regression during rollback window because Phase 1a does not dispatch agentmd entries.
|
|
112
|
+
- **Field flip:** because agentmd is filtered at `JobScheduler.start`, an emergency disable could be effected by deleting all `.instar/jobs/schedule/*.json` files (operator action). No data loss — the manifests are recoverable from git history.
|
|
113
|
+
- **No schema migration.** The disk format on `.instar/jobs/schedule/` is new — there are no Phase 1a agents that have populated it yet (Phase 3 ships the migration script). Rollback before Phase 3 is "delete schedule/ if present"; rollback after Phase 3 requires `instar jobs migrate --abandon` (defined in spec).
|
|
114
|
+
|
|
115
|
+
Estimated rollback: revert + patch release. Five minutes of build + publish.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Conclusion
|
|
120
|
+
|
|
121
|
+
Phase 1a is structural, additive, and rollback-cheap. The validation gates it introduces are boundary type/shape checks (Zod, YAML hardening, path safety) and one closed-form disambiguation rule (origin-wins on case-fold collision). No brittle blocking authority is added. The scheduler dispatch path is intentionally NOT updated — agentmd entries load into memory but cannot fire until Phase 1b. Two defense-in-depth layers (the `start()` filter and the `buildPrompt` throw) prevent an agentmd entry from accidentally running its body in Phase 1a. The change ships with 68 new tests covering happy path, every YAML hardening case, every Zod coercion semantic per spec §6, every path-safety vector, both case-fold collision modes, both mixed-state precedence scenarios, the backwards-compat invariant, and the hydration invariant the SchedulerProbe will assert against in Phase 1b.
|
|
122
|
+
|
|
123
|
+
The change is clear to ship.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Second-pass review
|
|
128
|
+
|
|
129
|
+
**Reviewer:** focused self-audit (no Task/Spawn tool surface in this build environment — the standard general-purpose subagent surface was unavailable; this is documented honestly here rather than fabricated)
|
|
130
|
+
**Independent read of the artifact: concur, with one verified caveat**
|
|
131
|
+
|
|
132
|
+
Independent pass over the artifact against the diff:
|
|
133
|
+
|
|
134
|
+
- **Decision-point inventory matches code.** Confirmed by re-reading `src/scheduler/JobLoader.ts`, `src/scheduler/AgentMdJobLoader.ts`, and `src/scheduler/JobScheduler.ts`. No undocumented decision-point touched. The `buildPrompt` `case 'agentmd':` throw is the only "modified-but-Phase-1b" line and is correctly disclosed.
|
|
135
|
+
- **Over-block claim verified by test.** `ACCEPTS anchor-like text inside string values (Bash & Read, *.md)` exists at `tests/unit/scheduler/JobLoader.agentmd.test.ts` and passes. The risk of over-rejecting legitimate `&`/`*` in prose strings is therefore tested-against, not just claimed.
|
|
136
|
+
- **Under-block claim verified by scope.** `commonBlockers` deep-validation is correctly deferred. The legacy `validateCommonBlockers` in `JobLoader.ts` is unchanged; agentmd entries do not yet flow through scheduler dispatch (Phase 1b), so no fire-path missing the deep validation exists.
|
|
137
|
+
- **Defense-in-depth on `buildPrompt`.** Re-read: `start()` filters `j.execute.type !== 'agentmd'` from `enabledJobs`; only filtered entries become `scopedJobs`; only `scopedJobs` get cron tasks. Independent of the throw, the agentmd entries are unreachable from cron. The throw is dead-code-by-design; this is correctly stated.
|
|
138
|
+
- **Backwards-compat scope verified.** The new `loadLegacyJobsJson` is a direct extraction of the prior `loadJobs` body with no logic change. Verified by reading both versions side-by-side. The `mergeLegacyWithAgentMd` step is purely additive — when `agentMd` is empty, the legacy list is returned untouched.
|
|
139
|
+
- **One verified caveat (not blocking):** the Zod preprocessor `IntField` rejects `1.5` as written, but a YAML number `1.5` parsed by FAILSAFE_SCHEMA returns the STRING `"1.5"`, not the number 1.5. The IntField preprocessor's `/^-?\d+$/` regex correctly rejects this. The test `rejects floats: '1.5'` covers the string path; the additional test `rejects floats: 1.5 (native number)` covers the number path. Both paths verified.
|
|
140
|
+
|
|
141
|
+
Concur. The change is structurally clean and ready to ship.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Evidence pointers
|
|
146
|
+
|
|
147
|
+
- Spec: `docs/specs/INSTAR-JOBS-AS-AGENTMD-SPEC.md` (approved 2026-05-12)
|
|
148
|
+
- Convergence report: `docs/specs/reports/instar-jobs-as-agentmd-convergence.md`
|
|
149
|
+
- New tests: `tests/unit/scheduler/JobLoader.agentmd.test.ts` (68 cases, all passing).
|
|
150
|
+
- New helper: `tests/unit/scheduler/agentmd-helpers.ts`.
|
|
151
|
+
- Backwards-compat verification: existing `tests/unit/JobLoader.test.ts` continues to pass unchanged.
|
|
152
|
+
- Logs (local): `node_modules/.bin/vitest run tests/unit/scheduler/JobLoader.agentmd.test.ts` → `Tests 68 passed (68)`.
|
|
153
|
+
- Fixup verified: `node_modules/.bin/vitest run tests/unit/scheduler-queue-edge.test.ts tests/unit/scheduler/JobLoader.agentmd.test.ts tests/unit/JobLoader.test.ts` → `Tests 120 passed (120)`. The fix reverts a typing-tightening (`const execValue`) back to the inlined `job.execute.value` form expected by `scheduler-queue-edge.test.ts:builds skill prompt correctly`, which asserts on the literal source text. No behavioral change; the agentmd case + exhaustive default remain.
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Side-Effects Review — project-scope Phase 1.7 skill surface
|
|
2
|
+
|
|
3
|
+
**Scope.** Closes the spec § Phase 1.7 user-facing surface. Rewrites
|
|
4
|
+
`.claude/skills/instar-project/SKILL.md` to cover all twelve canonical
|
|
5
|
+
commands, adds the three small HTTP routes the spec named but hadn't
|
|
6
|
+
materialized (`run-round`, `resume`, `abandon`), and realigns
|
|
7
|
+
`skillCommandForAction()` to the canonical command names.
|
|
8
|
+
|
|
9
|
+
## What's IN this PR
|
|
10
|
+
|
|
11
|
+
1. **`.claude/skills/instar-project/SKILL.md`** — full rewrite. Phase 1a
|
|
12
|
+
placeholder language for `/project next` is removed (the connect-the-dots
|
|
13
|
+
PR shipped the structured payload; the skill now documents that contract).
|
|
14
|
+
Every command from spec § 1.7 has a section header with its backing
|
|
15
|
+
endpoint, body schema, response codes, and a one-line rendering rule.
|
|
16
|
+
Final section pins the conversational-tone rule ("never paste a curl
|
|
17
|
+
command in a user-facing reply") that Echo's CLAUDE.md enforces.
|
|
18
|
+
2. **`POST /projects/:id/run-round`** — manual round trigger. On
|
|
19
|
+
`preflight.ok`, writes `autoAdvanceAt = now` to the named round and
|
|
20
|
+
returns 200 + `scheduledAt`. The auto-advance poller (wired in the
|
|
21
|
+
connect-the-dots PR) picks it up on its next tick (≤60s) and dispatches
|
|
22
|
+
`ProjectRoundExecution.runRound()`. No child-process spawn from the
|
|
23
|
+
request handler — keeps one fire path through one lock.
|
|
24
|
+
3. **`POST /projects/:id/resume`** — clears `haltedAt`/`haltReason`,
|
|
25
|
+
schedules the round via `autoAdvanceAt = now`, transitions
|
|
26
|
+
`project.status` from `'halted'`/`'abandoned'` back to `'active'`.
|
|
27
|
+
For `status: 'failed'` rounds at the 3-attempt cap, `{force: true}`
|
|
28
|
+
is required AND the `resumeAttempts` counter resets to zero on
|
|
29
|
+
accept. Returns 409 when the round is neither halted nor failed.
|
|
30
|
+
4. **`POST /projects/:id/abandon`** — sets `project.status = 'abandoned'`,
|
|
31
|
+
strips `autoAdvanceAt` from every remaining round so the poller
|
|
32
|
+
stops considering them, leaves each child's `pipelineStage`
|
|
33
|
+
untouched. Idempotent (`alreadyAbandoned: true` on repeat).
|
|
34
|
+
Refuses 409 when any round is `in-progress` — halt first.
|
|
35
|
+
5. **`skillCommandForAction()` realignment.** The connect-the-dots PR
|
|
36
|
+
returned `/project approve` and `/project drift-check` for the
|
|
37
|
+
`await-user-approval` and `run-drift-check` action verbs. Spec § 1.7
|
|
38
|
+
uses `/project ack` and `/project drift`; the mapping is updated so
|
|
39
|
+
the dashboard hints + the `/projects/:id/next` payload + the skill
|
|
40
|
+
stay in sync.
|
|
41
|
+
6. **Tests.** `tests/unit/instar-project-skill.test.ts` (28 assertions)
|
|
42
|
+
pins the SKILL.md contract — frontmatter, every spec § 1.7 command
|
|
43
|
+
has a header, every documented endpoint exists in `routes.ts`.
|
|
44
|
+
`tests/integration/projects-api.test.ts` adds 10 cases for the new
|
|
45
|
+
routes covering happy paths + every documented error case.
|
|
46
|
+
|
|
47
|
+
## What's explicitly DEFERRED
|
|
48
|
+
|
|
49
|
+
- **`ProjectRoundRunner.resume` / `.abandon` methods.** The new
|
|
50
|
+
endpoints mutate state via `tracker.update()` directly rather than
|
|
51
|
+
routing through a runner method. The runner's value-add is the
|
|
52
|
+
`preflight()` gate; resume/abandon don't have preflight semantics
|
|
53
|
+
(they're transitions OUT of stuck states, not transitions INTO an
|
|
54
|
+
autonomous run). If a future need surfaces — e.g., resume needs to
|
|
55
|
+
hold the round-runner lock briefly, or abandon needs to release a
|
|
56
|
+
worktree — those can land as runner methods then. Today, the
|
|
57
|
+
shorter path is cleaner.
|
|
58
|
+
- **Resume worktree cleanup.** Spec § 1.5 step "Halt switch" says
|
|
59
|
+
"Worktrees retained for inspection (cleanup deferred to user
|
|
60
|
+
`/project resume` or `/project abandon`)." The new resume/abandon
|
|
61
|
+
routes do NOT prune worktrees — the autonomous child the poller
|
|
62
|
+
fires next will recreate them lazily. Worktree GC is a separate
|
|
63
|
+
follow-up (registered as a child of the spec's deferral list).
|
|
64
|
+
- **No telemetry on action verbs.** The dashboard hint
|
|
65
|
+
(`skillCommandForAction()`) is read-only suggestion; we don't yet
|
|
66
|
+
count which verbs fire most frequently. A simple counter on the
|
|
67
|
+
`/projects/:id/next` handler would surface "what's the most common
|
|
68
|
+
unblocking step?" — interesting telemetry, but not Phase 1 scope.
|
|
69
|
+
- **Skill body translation hooks.** The SKILL.md is a single file; we
|
|
70
|
+
don't (yet) split per-command bodies into separate files the agent
|
|
71
|
+
can lazy-load. At ~10KB the file is well within the context budget
|
|
72
|
+
for a `/project` invocation. If the surface keeps growing past
|
|
73
|
+
Phase 1, splitting per-command is the natural next move.
|
|
74
|
+
|
|
75
|
+
## Side-effects review
|
|
76
|
+
|
|
77
|
+
**Over-block.** All three new routes are pure tracker.update() calls
|
|
78
|
+
with OCC. No child-process spawn, no git shell-out, no LLM call.
|
|
79
|
+
P99 latency in test runs ~20ms (single tracker write + JSON encode).
|
|
80
|
+
The skill body grew from ~3.5KB to ~10KB — adds one file read per
|
|
81
|
+
`/project` invocation; well below the context-window pressure
|
|
82
|
+
threshold.
|
|
83
|
+
|
|
84
|
+
**Under-block.** `resume` does not re-run drift before scheduling.
|
|
85
|
+
The poller's preflight (which fires after the route writes
|
|
86
|
+
`autoAdvanceAt`) re-runs drift if the cached verdict is stale (>24h)
|
|
87
|
+
per spec § Phase 1.4. So drift is enforced at fire time, not at
|
|
88
|
+
resume time — same level as the original `/project run-round`. This
|
|
89
|
+
is intentional: resume is a state mutation, not a fire trigger.
|
|
90
|
+
|
|
91
|
+
**Level-of-abstraction fit.** Each new route sits at the same layer
|
|
92
|
+
as the surrounding routes:
|
|
93
|
+
- `run-round` belongs alongside `halt` and `ack` (all three are
|
|
94
|
+
user-initiated state changes that thread through the runner).
|
|
95
|
+
- `resume` and `abandon` belong alongside `accept-partial` (all
|
|
96
|
+
three close out a specific round-end condition).
|
|
97
|
+
|
|
98
|
+
The skill is the highest layer — it's the conversational shell over
|
|
99
|
+
the HTTP API. SKILL.md is markdown the agent reads on `/project`
|
|
100
|
+
invocation, not executable code; the body's instructions ARE the
|
|
101
|
+
implementation.
|
|
102
|
+
|
|
103
|
+
**Signal vs authority.** The new routes are authority paths — they
|
|
104
|
+
mutate state. None of them consult an LLM signal. `run-round` does
|
|
105
|
+
delegate to `ProjectRoundRunner.preflight()` for the deterministic
|
|
106
|
+
gate checks; that's the spec's single-chokepoint pattern, not a
|
|
107
|
+
signal/authority mix.
|
|
108
|
+
|
|
109
|
+
**Interactions.**
|
|
110
|
+
- `run-round` + `auto-advance poller`: the route writes `autoAdvanceAt`,
|
|
111
|
+
the poller reads it. The poller's `inFlight: Set<projectId>` guard
|
|
112
|
+
(shipped in connect-the-dots) ensures a manual run-round followed
|
|
113
|
+
by a poller tick doesn't double-fire.
|
|
114
|
+
- `resume` + `halt`: idempotent in either direction. `halt` writes
|
|
115
|
+
`haltedAt`; `resume` strips it. Repeat halts on an already-halted
|
|
116
|
+
round are no-ops; repeat resumes on an already-resumed round
|
|
117
|
+
return 409 ("round is not halted or failed").
|
|
118
|
+
- `abandon` + `claim-ownership`: abandon sets `project.status =
|
|
119
|
+
'abandoned'`; the auto-advance poller's owner filter already
|
|
120
|
+
excludes abandoned projects. A peer claiming ownership of an
|
|
121
|
+
abandoned project would just re-claim — the poller still skips
|
|
122
|
+
abandoned-status projects regardless of owner.
|
|
123
|
+
- `skillCommandForAction()` realignment: existing test
|
|
124
|
+
`expect(typeof res.body.skillCommand).toBe('string')` doesn't
|
|
125
|
+
assert exact strings, so the rename is backwards-compatible at
|
|
126
|
+
the test level. Dashboard consumers render the string verbatim —
|
|
127
|
+
they get `/project ack` instead of `/project approve`, which is
|
|
128
|
+
what spec § 1.7 documented.
|
|
129
|
+
|
|
130
|
+
**Rollback cost.** Three options:
|
|
131
|
+
1. **Skill-only rollback** — restore the prior SKILL.md from
|
|
132
|
+
`git show HEAD~1:.claude/skills/instar-project/SKILL.md`. Agent
|
|
133
|
+
reverts to Phase 1a read-only behavior; new HTTP routes still
|
|
134
|
+
exist but are not surfaced. Zero state risk.
|
|
135
|
+
2. **Routes-only rollback** — `git revert` the three new route
|
|
136
|
+
handlers. Skill still references them; agent surfaces 404 from
|
|
137
|
+
`/project run-round`, `/project resume`, `/project abandon`.
|
|
138
|
+
Surface-but-not-functional, but no data corruption — state
|
|
139
|
+
mutations all go through `tracker.update()` with OCC, which is
|
|
140
|
+
already proven safe.
|
|
141
|
+
3. **Full revert** — drop the whole commit. No on-disk state from
|
|
142
|
+
this PR persists (the abandon/halt/resume state fields all
|
|
143
|
+
pre-exist in the `Initiative` schema from Phase 1a).
|
|
144
|
+
|
|
145
|
+
All three are clean. The PR introduces no new persisted state, no
|
|
146
|
+
new file paths, no new background work — it adds surface area over
|
|
147
|
+
the existing state machine.
|
|
148
|
+
|
|
149
|
+
## Files touched
|
|
150
|
+
|
|
151
|
+
- `.claude/skills/instar-project/SKILL.md` (rewritten)
|
|
152
|
+
- `src/server/routes.ts` (three new route handlers + one mapping fix)
|
|
153
|
+
- `tests/integration/projects-api.test.ts` (10 new cases)
|
|
154
|
+
- `tests/unit/instar-project-skill.test.ts` (new file, 28 assertions)
|
|
155
|
+
- `upgrades/NEXT.md` (new release notes)
|
|
156
|
+
|
|
157
|
+
## Spec references
|
|
158
|
+
|
|
159
|
+
- `docs/specs/PROJECT-SCOPE-SPEC.md` § Phase 1.7 — canonical
|
|
160
|
+
command list.
|
|
161
|
+
- `docs/specs/PROJECT-SCOPE-SPEC.md` § Phase 1.5 — runner preflight
|
|
162
|
+
contract; halt/resume worktree-retention rule.
|
|
163
|
+
- `docs/specs/PROJECT-SCOPE-SPEC.md` § Phase 1.3 — HTTP layer
|
|
164
|
+
doesn't enforce gates the runner already enforces. `run-round`
|
|
165
|
+
delegates to `preflight()` cleanly.
|
|
166
|
+
|
|
167
|
+
## Coverage
|
|
168
|
+
|
|
169
|
+
- Unit: structural coverage of SKILL.md + endpoint existence —
|
|
170
|
+
`tests/unit/instar-project-skill.test.ts`.
|
|
171
|
+
- Integration: happy + error paths for all three new routes against
|
|
172
|
+
the real `AgentServer` + `InitiativeTracker` + `ProjectRoundRunner`
|
|
173
|
+
— `tests/integration/projects-api.test.ts`.
|
|
174
|
+
- Lint: clean.
|
|
175
|
+
- Type-check: clean (new `RoundStatus` import).
|