instar 1.3.757 → 1.3.758

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.
Files changed (31) hide show
  1. package/dist/core/PostUpdateMigrator.d.ts +1 -0
  2. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  3. package/dist/core/PostUpdateMigrator.js +182 -0
  4. package/dist/core/PostUpdateMigrator.js.map +1 -1
  5. package/dist/core/instarSettingsHooks.d.ts.map +1 -1
  6. package/dist/core/instarSettingsHooks.js +12 -0
  7. package/dist/core/instarSettingsHooks.js.map +1 -1
  8. package/dist/core/types.d.ts +16 -0
  9. package/dist/core/types.d.ts.map +1 -1
  10. package/dist/core/types.js.map +1 -1
  11. package/dist/scheduler/AgentMdJobLoader.d.ts +4 -0
  12. package/dist/scheduler/AgentMdJobLoader.d.ts.map +1 -1
  13. package/dist/scheduler/AgentMdJobLoader.js +2 -0
  14. package/dist/scheduler/AgentMdJobLoader.js.map +1 -1
  15. package/dist/scheduler/InstallBuiltinJobs.d.ts.map +1 -1
  16. package/dist/scheduler/InstallBuiltinJobs.js +3 -0
  17. package/dist/scheduler/InstallBuiltinJobs.js.map +1 -1
  18. package/dist/scheduler/JobScheduler.d.ts.map +1 -1
  19. package/dist/scheduler/JobScheduler.js +17 -5
  20. package/dist/scheduler/JobScheduler.js.map +1 -1
  21. package/dist/scheduler/buildPerSlugManifest.d.ts +4 -0
  22. package/dist/scheduler/buildPerSlugManifest.d.ts.map +1 -1
  23. package/dist/scheduler/buildPerSlugManifest.js +2 -0
  24. package/dist/scheduler/buildPerSlugManifest.js.map +1 -1
  25. package/package.json +1 -1
  26. package/scripts/doorway-scan.mjs +785 -0
  27. package/scripts/model-registry-freshness.manifest.json +17 -2
  28. package/src/data/builtin-manifest.json +20 -20
  29. package/src/scaffold/templates/jobs/instar/doorway-scan.md +32 -0
  30. package/upgrades/1.3.758.md +37 -0
  31. package/upgrades/side-effects/doorway-model-registry-inc2.md +96 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$comment": "Doorway/Model Knowledge Registry — canonical/reviewed layer + staleness/drift gate for Instar's per-provider 'capable/latest' model pins. This file is the SINGLE human-edit surface that keeps the model registry from silently rotting. See docs/LLM-ROUTING-REGISTRY.md, docs/specs/DOORWAY-MODEL-KNOWLEDGE-REGISTRY-SPEC.md, and scripts/lint-model-registry-freshness.mjs. Two teeth: (1) STALENESS — lastReviewedAt must be within stalenessWindowDays; (2) DRIFT — every pinned capable model id must be a member of its door's DERIVED frontier set (the ids in doors[door].topModels[] carrying frontier:true — see §1.4 of the spec). topModels is the ONE hand-edited source of truth; the frontier set is a view of it and cannot diverge by construction. To pass the check a human must confirm each capable pin is genuinely current frontier AND bump lastReviewedAt. An un-reviewed list fails loudly.",
3
3
  "registrySchemaVersion": 2,
4
- "$schemaNote": "registrySchemaVersion 2 = the enriched Doorway/Model Knowledge Registry. Rollout increment 1 (spec §Rollout step 1): the per-door topModels[] frontier-derivation layer + the derived-frontier lint (report mode). The live scan-state (.instar/state/doorway-scan.json), the deterministic prober, the per-door probe{} block, candidateDoorways[], the scan job, and GET /doorways are LATER increments (spec §Rollout steps 2-3) and are NOT part of this schema version's consumers yet.",
4
+ "$schemaNote": "registrySchemaVersion 2 = the enriched Doorway/Model Knowledge Registry. Increment 1 (spec §Rollout step 1) added the per-door topModels[] frontier-derivation layer + the derived-frontier lint (report mode). Increment 2 (spec §Rollout step 2) ADDS the per-door probe{} block + candidateDoorways[] below — the metadata the deterministic prober (scripts/doorway-scan.mjs) reads to know HOW to re-verify each door — plus the live scan-state (.instar/state/doorway-scan.json) + the dark (enabled:false) doorway-scan job. Both additions are backward-compatible: the freshness lint ignores probe{}/candidateDoorways entirely (it reads only topModels/frontier/pins). GET /doorways is a LATER increment (spec §Rollout step 3) and is NOT wired yet.",
5
5
  "lastReviewedAt": "2026-07-03",
6
6
  "stalenessWindowDays": 45,
7
7
  "enforcement": "report",
@@ -11,6 +11,7 @@
11
11
  "name": "Claude Code CLI",
12
12
  "accessMethod": "Claude Code CLI (subscription/OAuth)",
13
13
  "status": "alive",
14
+ "probe": { "kind": "cli-version", "bin": "claude", "metered": false, "freeDefaultCovered": true },
14
15
  "topModels": [
15
16
  { "id": "claude-opus-4-8", "role": "capable-anthropic", "frontier": true, "pricing": null, "verifiedAt": "carried-over-from-allowlist" },
16
17
  { "id": "claude-fable-5", "role": "ultra-anthropic", "frontier": true, "pricing": null, "verifiedAt": "carried-over-from-allowlist" }
@@ -21,6 +22,7 @@
21
22
  "name": "Anthropic headless (claude -p)",
22
23
  "accessMethod": "claude -p one-shot (Agent SDK credit pot)",
23
24
  "status": "alive",
25
+ "probe": { "kind": "cli-version", "bin": "claude", "metered": false, "freeDefaultCovered": true },
24
26
  "topModels": [
25
27
  { "id": "claude-opus-4-8", "role": "capable-anthropic", "frontier": true, "pricing": null, "verifiedAt": "carried-over-from-allowlist" },
26
28
  { "id": "claude-sonnet-4-6", "role": "balanced-anthropic", "frontier": true, "pricing": null, "verifiedAt": "carried-over-from-allowlist" },
@@ -32,6 +34,7 @@
32
34
  "name": "Codex CLI",
33
35
  "accessMethod": "codex exec (ChatGPT account)",
34
36
  "status": "referenced-not-installed",
37
+ "probe": { "kind": "cli-version", "bin": "codex", "metered": false, "freeDefaultCovered": false },
35
38
  "topModels": [
36
39
  { "id": "gpt-5.5", "role": "capable-openai", "frontier": true, "pricing": null, "verifiedAt": "carried-over-from-allowlist" }
37
40
  ],
@@ -41,6 +44,7 @@
41
44
  "name": "Gemini CLI",
42
45
  "accessMethod": "gemini -m <model> (Homebrew formula, OAuth)",
43
46
  "status": "alive-but-deprecated-formula",
47
+ "probe": { "kind": "cli-version", "bin": "gemini", "metered": false, "freeDefaultCovered": true },
44
48
  "topModels": [
45
49
  { "id": "gemini-3.1-pro-preview", "role": "capable-google", "frontier": true, "pricing": null, "verifiedAt": "carried-over-from-allowlist" }
46
50
  ],
@@ -50,6 +54,7 @@
50
54
  "name": "pi CLI",
51
55
  "accessMethod": "pi --model <provider>/<id> (multi-provider: openai-codex, openrouter, ...)",
52
56
  "status": "alive",
57
+ "probe": { "kind": "cli-version", "bin": "pi", "metered": false, "vaultKeyName": "metered_openrouter_bench", "freeDefaultCovered": false },
53
58
  "topModels": [],
54
59
  "note": "Multi-provider consolidation door. Can reach OpenRouter (one door to many frontier models; vault key metered_openrouter_bench present). pi policy DENIES anthropic/claude via openrouter passthrough by default to protect subscription billing. No pin references this door yet, so topModels is empty (no frontier set to derive); the first enabled scan enumerates observed models for operator classification."
55
60
  }
@@ -90,5 +95,15 @@
90
95
  ],
91
96
  "$flaggedStaleNote": "Both prior flagged pins RESOLVED (operator-confirmed 2026-07-03): gemini capable swapped gemini-2.5-pro -> gemini-3.1-pro-preview (pin + topModels updated in the same change); codex capable stays gpt-5.5 (GA flagship — gpt-5.6-sol is preview-only/gov-gated/unreachable, NOT pinned). Nothing pending confirmation.",
92
97
  "flaggedStale": [],
93
- "$frontierAllowlistNote": "The hand-maintained frontierAllowlist{} was SUPERSEDED by derivation (spec §1.4): the frontier set for each door is now the ids in doors[door].topModels[] carrying frontier:true. This removes a second hand-maintained list (the rot this registry fights). The lint keeps a backward-compat path: a door with a literal frontierAllowlist[door] and NO topModels behaves exactly as before; a door with BOTH emits a transition finding so the old hand-list can't silently linger."
98
+ "$frontierAllowlistNote": "The hand-maintained frontierAllowlist{} was SUPERSEDED by derivation (spec §1.4): the frontier set for each door is now the ids in doors[door].topModels[] carrying frontier:true. This removes a second hand-maintained list (the rot this registry fights). The lint keeps a backward-compat path: a door with a literal frontierAllowlist[door] and NO topModels behaves exactly as before; a door with BOTH emits a transition finding so the old hand-list can't silently linger.",
99
+ "$candidateDoorwaysNote": "candidateDoorways[] = the doorways the scan should PROBE FOR (spec §1.2/§2.5), INCLUDING ones not yet present in doors{}. A candidate reachable but absent from doors is surfaced as a newly-configured-doorway. The prober only activates a candidate whose probe.kind it supports; a candidate with no door entry / unsupported kind is recorded, never silently treated as reachable. openrouter / gemini-direct-api below are candidate-only (no door entry yet) so the candidate-detection machinery has real inputs to exercise.",
100
+ "candidateDoorways": [
101
+ "claude-code",
102
+ "anthropic-headless",
103
+ "codex-cli",
104
+ "gemini-cli",
105
+ "pi-cli",
106
+ "openrouter",
107
+ "gemini-direct-api"
108
+ ]
94
109
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-07-04T18:41:14.577Z",
5
- "instarVersion": "1.3.757",
4
+ "generatedAt": "2026-07-04T19:36:12.853Z",
5
+ "instarVersion": "1.3.758",
6
6
  "entryCount": 202,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -11,7 +11,7 @@
11
11
  "domain": "identity",
12
12
  "sourcePath": "src/core/PostUpdateMigrator.ts",
13
13
  "installedPath": ".instar/hooks/instar/session-start.sh",
14
- "contentHash": "3a280757ac9c2c4a35cf63aa4ba2f4a954ee85be842715fd93a92aa0881325e3",
14
+ "contentHash": "abb2e8ea9b3e906f37767153ca1ef7442cfc92ba27a50a7eaaa09596c8d5eede",
15
15
  "since": "2025-01-01"
16
16
  },
17
17
  "hook:dangerous-command-guard": {
@@ -20,7 +20,7 @@
20
20
  "domain": "safety",
21
21
  "sourcePath": "src/core/PostUpdateMigrator.ts",
22
22
  "installedPath": ".instar/hooks/instar/dangerous-command-guard.sh",
23
- "contentHash": "3a280757ac9c2c4a35cf63aa4ba2f4a954ee85be842715fd93a92aa0881325e3",
23
+ "contentHash": "abb2e8ea9b3e906f37767153ca1ef7442cfc92ba27a50a7eaaa09596c8d5eede",
24
24
  "since": "2025-01-01"
25
25
  },
26
26
  "hook:grounding-before-messaging": {
@@ -29,7 +29,7 @@
29
29
  "domain": "safety",
30
30
  "sourcePath": "src/core/PostUpdateMigrator.ts",
31
31
  "installedPath": ".instar/hooks/instar/grounding-before-messaging.sh",
32
- "contentHash": "3a280757ac9c2c4a35cf63aa4ba2f4a954ee85be842715fd93a92aa0881325e3",
32
+ "contentHash": "abb2e8ea9b3e906f37767153ca1ef7442cfc92ba27a50a7eaaa09596c8d5eede",
33
33
  "since": "2025-01-01"
34
34
  },
35
35
  "hook:compaction-recovery": {
@@ -38,7 +38,7 @@
38
38
  "domain": "identity",
39
39
  "sourcePath": "src/core/PostUpdateMigrator.ts",
40
40
  "installedPath": ".instar/hooks/instar/compaction-recovery.sh",
41
- "contentHash": "3a280757ac9c2c4a35cf63aa4ba2f4a954ee85be842715fd93a92aa0881325e3",
41
+ "contentHash": "abb2e8ea9b3e906f37767153ca1ef7442cfc92ba27a50a7eaaa09596c8d5eede",
42
42
  "since": "2025-01-01"
43
43
  },
44
44
  "hook:external-operation-gate": {
@@ -47,7 +47,7 @@
47
47
  "domain": "safety",
48
48
  "sourcePath": "src/core/PostUpdateMigrator.ts",
49
49
  "installedPath": ".instar/hooks/instar/external-operation-gate.js",
50
- "contentHash": "3a280757ac9c2c4a35cf63aa4ba2f4a954ee85be842715fd93a92aa0881325e3",
50
+ "contentHash": "abb2e8ea9b3e906f37767153ca1ef7442cfc92ba27a50a7eaaa09596c8d5eede",
51
51
  "since": "2025-01-01"
52
52
  },
53
53
  "hook:deferral-detector": {
@@ -56,7 +56,7 @@
56
56
  "domain": "safety",
57
57
  "sourcePath": "src/core/PostUpdateMigrator.ts",
58
58
  "installedPath": ".instar/hooks/instar/deferral-detector.js",
59
- "contentHash": "3a280757ac9c2c4a35cf63aa4ba2f4a954ee85be842715fd93a92aa0881325e3",
59
+ "contentHash": "abb2e8ea9b3e906f37767153ca1ef7442cfc92ba27a50a7eaaa09596c8d5eede",
60
60
  "since": "2025-01-01"
61
61
  },
62
62
  "hook:self-stop-guard": {
@@ -65,7 +65,7 @@
65
65
  "domain": "coherence",
66
66
  "sourcePath": "src/core/PostUpdateMigrator.ts",
67
67
  "installedPath": ".instar/hooks/instar/self-stop-guard.js",
68
- "contentHash": "3a280757ac9c2c4a35cf63aa4ba2f4a954ee85be842715fd93a92aa0881325e3",
68
+ "contentHash": "abb2e8ea9b3e906f37767153ca1ef7442cfc92ba27a50a7eaaa09596c8d5eede",
69
69
  "since": "2025-01-01"
70
70
  },
71
71
  "hook:post-action-reflection": {
@@ -74,7 +74,7 @@
74
74
  "domain": "evolution",
75
75
  "sourcePath": "src/core/PostUpdateMigrator.ts",
76
76
  "installedPath": ".instar/hooks/instar/post-action-reflection.js",
77
- "contentHash": "3a280757ac9c2c4a35cf63aa4ba2f4a954ee85be842715fd93a92aa0881325e3",
77
+ "contentHash": "abb2e8ea9b3e906f37767153ca1ef7442cfc92ba27a50a7eaaa09596c8d5eede",
78
78
  "since": "2025-01-01"
79
79
  },
80
80
  "hook:external-communication-guard": {
@@ -83,7 +83,7 @@
83
83
  "domain": "safety",
84
84
  "sourcePath": "src/core/PostUpdateMigrator.ts",
85
85
  "installedPath": ".instar/hooks/instar/external-communication-guard.js",
86
- "contentHash": "3a280757ac9c2c4a35cf63aa4ba2f4a954ee85be842715fd93a92aa0881325e3",
86
+ "contentHash": "abb2e8ea9b3e906f37767153ca1ef7442cfc92ba27a50a7eaaa09596c8d5eede",
87
87
  "since": "2025-01-01"
88
88
  },
89
89
  "hook:scope-coherence-collector": {
@@ -92,7 +92,7 @@
92
92
  "domain": "coherence",
93
93
  "sourcePath": "src/core/PostUpdateMigrator.ts",
94
94
  "installedPath": ".instar/hooks/instar/scope-coherence-collector.js",
95
- "contentHash": "3a280757ac9c2c4a35cf63aa4ba2f4a954ee85be842715fd93a92aa0881325e3",
95
+ "contentHash": "abb2e8ea9b3e906f37767153ca1ef7442cfc92ba27a50a7eaaa09596c8d5eede",
96
96
  "since": "2025-01-01"
97
97
  },
98
98
  "hook:scope-coherence-checkpoint": {
@@ -101,7 +101,7 @@
101
101
  "domain": "coherence",
102
102
  "sourcePath": "src/core/PostUpdateMigrator.ts",
103
103
  "installedPath": ".instar/hooks/instar/scope-coherence-checkpoint.js",
104
- "contentHash": "3a280757ac9c2c4a35cf63aa4ba2f4a954ee85be842715fd93a92aa0881325e3",
104
+ "contentHash": "abb2e8ea9b3e906f37767153ca1ef7442cfc92ba27a50a7eaaa09596c8d5eede",
105
105
  "since": "2025-01-01"
106
106
  },
107
107
  "hook:free-text-guard": {
@@ -110,7 +110,7 @@
110
110
  "domain": "safety",
111
111
  "sourcePath": "src/core/PostUpdateMigrator.ts",
112
112
  "installedPath": ".instar/hooks/instar/free-text-guard.sh",
113
- "contentHash": "3a280757ac9c2c4a35cf63aa4ba2f4a954ee85be842715fd93a92aa0881325e3",
113
+ "contentHash": "abb2e8ea9b3e906f37767153ca1ef7442cfc92ba27a50a7eaaa09596c8d5eede",
114
114
  "since": "2025-01-01"
115
115
  },
116
116
  "hook:claim-intercept": {
@@ -119,7 +119,7 @@
119
119
  "domain": "coherence",
120
120
  "sourcePath": "src/core/PostUpdateMigrator.ts",
121
121
  "installedPath": ".instar/hooks/instar/claim-intercept.js",
122
- "contentHash": "3a280757ac9c2c4a35cf63aa4ba2f4a954ee85be842715fd93a92aa0881325e3",
122
+ "contentHash": "abb2e8ea9b3e906f37767153ca1ef7442cfc92ba27a50a7eaaa09596c8d5eede",
123
123
  "since": "2025-01-01"
124
124
  },
125
125
  "hook:claim-intercept-response": {
@@ -128,7 +128,7 @@
128
128
  "domain": "coherence",
129
129
  "sourcePath": "src/core/PostUpdateMigrator.ts",
130
130
  "installedPath": ".instar/hooks/instar/claim-intercept-response.js",
131
- "contentHash": "3a280757ac9c2c4a35cf63aa4ba2f4a954ee85be842715fd93a92aa0881325e3",
131
+ "contentHash": "abb2e8ea9b3e906f37767153ca1ef7442cfc92ba27a50a7eaaa09596c8d5eede",
132
132
  "since": "2025-01-01"
133
133
  },
134
134
  "hook:stop-gate-router": {
@@ -137,7 +137,7 @@
137
137
  "domain": "safety",
138
138
  "sourcePath": "src/core/PostUpdateMigrator.ts",
139
139
  "installedPath": ".instar/hooks/instar/stop-gate-router.js",
140
- "contentHash": "3a280757ac9c2c4a35cf63aa4ba2f4a954ee85be842715fd93a92aa0881325e3",
140
+ "contentHash": "abb2e8ea9b3e906f37767153ca1ef7442cfc92ba27a50a7eaaa09596c8d5eede",
141
141
  "since": "2025-01-01"
142
142
  },
143
143
  "hook:auto-approve-permissions": {
@@ -146,7 +146,7 @@
146
146
  "domain": "safety",
147
147
  "sourcePath": "src/core/PostUpdateMigrator.ts",
148
148
  "installedPath": ".instar/hooks/instar/auto-approve-permissions.js",
149
- "contentHash": "3a280757ac9c2c4a35cf63aa4ba2f4a954ee85be842715fd93a92aa0881325e3",
149
+ "contentHash": "abb2e8ea9b3e906f37767153ca1ef7442cfc92ba27a50a7eaaa09596c8d5eede",
150
150
  "since": "2025-01-01"
151
151
  },
152
152
  "job:health-check": {
@@ -1562,7 +1562,7 @@
1562
1562
  "type": "subsystem",
1563
1563
  "domain": "updates",
1564
1564
  "sourcePath": "src/core/PostUpdateMigrator.ts",
1565
- "contentHash": "3a280757ac9c2c4a35cf63aa4ba2f4a954ee85be842715fd93a92aa0881325e3",
1565
+ "contentHash": "abb2e8ea9b3e906f37767153ca1ef7442cfc92ba27a50a7eaaa09596c8d5eede",
1566
1566
  "since": "2025-01-01"
1567
1567
  },
1568
1568
  "subsystem:scheduler": {
@@ -1570,7 +1570,7 @@
1570
1570
  "type": "subsystem",
1571
1571
  "domain": "scheduling",
1572
1572
  "sourcePath": "src/scheduler/JobScheduler.ts",
1573
- "contentHash": "8f526a365e7292029224135303525e89920435ced45b292cc4c50e47dc297ea4",
1573
+ "contentHash": "0c92bf7ce40b2efdede00ee90f9c0bf8997c787691ae0054d6479ad96f28be67",
1574
1574
  "since": "2025-01-01"
1575
1575
  },
1576
1576
  "subsystem:project-mapper": {
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: Doorway Scan
3
+ description: "Cadenced live re-probe of every doorway + top-model map refresh + drift diff. A deterministic prober (scripts/doorway-scan.mjs) probes CLIs (which/--version/model-list) and free model-list APIs, updates the per-agent live scan-state (.instar/state/doorway-scan.json), diffs against the previous scan, and raises ONE operator attention item with ONLY the changes (jargon-safe body + a private-view link to the raw diff). It NEVER auto-edits canonical source. Free probes by default; metered liveness / web-verify are opt-in, budget-capped, human-manual-only, and refuse on an unknown price. Ships OFF by default (enabled:false). perMachineIndependent — each machine scans its own disk. Spec: docs/specs/DOORWAY-MODEL-KNOWLEDGE-REGISTRY-SPEC.md §2."
4
+ schedule: "0 4 * * 1"
5
+ priority: low
6
+ expectedDurationMinutes: 10
7
+ model: haiku
8
+ supervision: tier1
9
+ enabled: false
10
+ perMachineIndependent: true
11
+ tags:
12
+ - cat:maintenance
13
+ - role:worker
14
+ - exec:prompt
15
+ gate: curl -sf http://localhost:${INSTAR_PORT:-4042}/health >/dev/null 2>&1
16
+ toolAllowlist: ["Bash"]
17
+ unrestrictedTools: false
18
+ mcpAccess: none
19
+ ---
20
+ Run one doorway scan. This job only INVOKES the deterministic prober and sanity-checks that it produced a well-formed result — the prober owns 100% of the network I/O, secret handling, timeouts, sanitization, and delivery (it performs the POST /view + POST /attention itself, in-process). You have Bash ONLY — no Edit/Write tool — and a PreToolUse command-allowlist guard restricts this session's Bash to the sanctioned prober invocation + the /health gate + read-only plumbing. Do NOT try to edit any source file; this job NEVER auto-applies a change to the canonical registry (a stale map surfaces as a maintainer diff for the operator, never an edit).
21
+
22
+ 1. **Prober-presence gate.** The prober is instar source — present only on a source-carrying agent (a maintainer/dev agent like Echo, and the fixture repos), NOT on a pure end-user agent. Run this SINGLE command (the command-allowlist guard permits only simple invocations — never chain it with `&&`/`||`/`;`):
23
+ `test -f scripts/doorway-scan.mjs`
24
+ If it exits NON-ZERO (the prober is absent), this agent carries no prober — EXIT SILENTLY, there is nothing to do (the honest no-op path every non-source agent takes). Do not message anyone. Only if it exits zero (present) do you proceed to step 2.
25
+
26
+ 2. **Run the deterministic prober at the free scope (the ONLY scope the scheduled cadence runs).** This is a FIXED literal invocation — never substitute the scope, never append a metered scope; a metered scope spends money and is human-manual-only:
27
+ `node scripts/doorway-scan.mjs --scope free-probes`
28
+ The prober probes each configured door, updates the machine-local scan-state (`.instar/state/doorway-scan.json`), diffs against the last-surfaced baseline, and — only if there is a real, debounced, live-corroborated change — raises ONE attention item itself, in-process, with a machine-qualified `sourceContext` (`doorway-scan:<machineId>`) so two machines' findings never coalesce into one row. Free model-LIST probes spend ZERO tokens; the scheduled cadence spends no metered budget.
29
+
30
+ 3. **Tier-1 supervision (your job) — sanity-check the run, do NOT re-surface anything yourself.** Confirm the prober exited cleanly and wrote/refreshed the scan-state. A crashed/partial run is NOT a signal — note it once and exit; the next cadence retries (the prober is fail-safe per-probe, and a completely-failed scan is handled by the prober's own breaker, which retries on a widening backoff and escalates ONE deduped item only after 3 consecutive complete failures). The prober already delivered any diff in-process — you do NOT build or POST an attention item, and you do NOT relay progress to Telegram.
31
+
32
+ 4. Exit. This job produces a review artifact for the operator (a maintainer diff), not a running commentary. A clean scan is not news — surface nothing. The canonical map changes only when the operator folds a surfaced diff via instar-dev and re-runs the freshness lint; this job never touches source.
@@ -0,0 +1,37 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ Doorway/Model Knowledge Registry — rollout increment 2 (the deterministic prober + live scan-state + the scan job, per `docs/specs/DOORWAY-MODEL-KNOWLEDGE-REGISTRY-SPEC.md` §Rollout step 2). **All of it ships DARK**: the scan job is `enabled:false` and no cadence runs until an operator enables it (rollout step 4). Nothing spends metered budget, mutates source, or changes model routing.
9
+
10
+ - **The deterministic prober** (`scripts/doorway-scan.mjs`, spec §2.0) — the security spine. It owns 100% of the network I/O, secret handling, timeouts, size-caps, sanitization, the scan-state read/write, the diff/debounce/breaker, and the in-process `POST /view` + `POST /attention` delivery; a tier-1 job session only invokes it. Secret-safe (vault key NAMES only; a value is used in an in-process request header, never in argv/environ/scan-state). Money is quadruple-gated: the scheduled cadence runs a fixed `--scope free-probes` (zero metered spend), and a metered scope is refused unless a manual-marker env AND a positive `budgetCapUsd` AND a known, fresh price are all present — plus a self-standing scheduled-session refusal that holds even if the §2.7 guard fails open.
11
+ - **Machine-local live scan-state** (`.instar/state/doorway-scan.json`, spec §1.3) — with an EXHAUSTIVE `loadScanState()` read-clamp that re-validates every field on every load (the on-disk plaintext file is treated as untrusted a local writer could poison after a clamped write), plus a coverage test so the clamp can't silently regrow holes.
12
+ - **Diff / debounce / breaker** (spec §2.3/§2.6) — an asymmetric 2-scan debounce (an alarming true→false flip needs corroboration; a recovery flips immediately), a `consecutiveCompleteFailures` breaker that retries on a widening backoff and escalates ONE deduped item only after 3 consecutive zero-door scans (P22 self-heal-before-notify), a tone-gate-safe jargon-safe attention body (raw diff in a private view; never a localhost/token-bearing link), and delivery robustness (the diff baseline advances only on a confirmed 2xx, with an initial-seed carve-out — no silent notice loss, no fresh-state flood).
13
+ - **The dark scan job template** (`src/scaffold/templates/jobs/instar/doorway-scan.md`, `enabled:false`, tier-1/haiku, `toolAllowlist:["Bash"]`) + the new **`perMachineIndependent` job-def flag** (spec §2.8/D11) threaded through `JobDefinition` → `PerSlugManifest` → `buildPerSlugManifest` → `InstallBuiltinJobs` → `manifestToJobDefinition`, and honored at `JobScheduler.triggerJob` (it skips the global jobSlug claim/lease so every machine scans its own disk).
14
+ - **The §2.7 PreToolUse command-allowlist guard** (`doorway-scan-guard.js`, installed via the always-overwrite `instar/` migration path) — a genuine stateful shell lexer (not a regex) that permits ONLY the sanctioned prober invocation + a host-pinned localhost curl + read-only `test -f`/`cat`/`jq -r`, refusing every write/exec/spend primitive. Scope resolution fails OPEN (never blocks an unrelated session); command matching fails CLOSED inside the doorway-scan session.
15
+ - **Manifest enrichment** (`scripts/model-registry-freshness.manifest.json`) — additively added the per-door `probe{}` block + `candidateDoorways[]` the prober reads to know HOW to re-verify each door. Backward-compatible: the freshness lint ignores both.
16
+
17
+ `GET /doorways`, the CLAUDE.md awareness block, and the `maintenance.doorwayScan` config-knob are increment 3; flipping the lint to `strict` + ratifying the standard is increment 5. Neither is in this increment.
18
+
19
+ ## What to Tell Your User
20
+
21
+ Nothing changes for you right now — this increment ships **switched off**. It lands the machinery for a future opt-in weekly "doorway scan" that will quietly re-check which AI models each of your doorways (Claude Code, Codex, Gemini, a paid API key, …) can still reach, keep the map current, and send you ONE plain-English heads-up only when something actually changes. Until you deliberately turn the job on (a later step), it does no probing, spends no money, and sends no messages. There is nothing to do and nothing new to see.
22
+
23
+ ## Summary of New Capabilities
24
+
25
+ - **(Dark / off by default)** A deterministic doorway prober + a per-machine live "what models can I reach?" scan-state, with an asymmetric debounce, a self-healing zero-doors breaker, and no-silent-loss delivery — none of it active until the operator enables the `doorway-scan` job.
26
+ - **A new `perMachineIndependent` job flag** so a per-machine-local job (like the doorway scan) runs on every machine independently instead of the lease electing just one.
27
+ - **A structural command-allowlist guard** that keeps the (future, opt-in) scan session from ever editing source or spending money — a genuine shell-parser allowlist that fails open outside that one session and fails closed inside it.
28
+
29
+ ## Evidence
30
+
31
+ - `tests/unit/doorway-scan-prober.test.ts` (45) — clamp/sanitize, the exhaustive read-funnel + coverage, the P20/L5 probeStatus mapping, the asymmetric debounce (incl. flip-then-flip-back-clears), the diff, the breaker + backoff, the fail-closed money gate (D8/D9/D10 + pricing staleness + self-standing scheduled refusal), tone-gate-safe body + private-view XSS neutralization, the delivery-robustness planner, and the injected-dep network probes.
32
+ - `tests/unit/doorway-scan-guard.test.ts` (30) — drives the REAL deployed hook as a subprocess across the full §2.7 matrix: sanctioned shapes PERMITTED (incl. quoted-arg / collapsed-whitespace normalizations); cp/interpreters/perl/git-checkout/heredoc/compound/substitution/env-prefix/redirect/output-redirect/non-localhost-curl/metered-scope all REFUSED; scope fails OPEN; command matching fails CLOSED.
33
+ - `tests/unit/doorway-scan-job-template.test.ts` (11) — enabled:false, tier1/haiku, perMachineIndependent:true, weekday-pinned cron, Bash-only toolAllowlist, the fixed `--scope free-probes` literal, no `+liveness`/`+web-verify` string, the prober-presence gate, no data-carrying curl.
34
+ - `tests/unit/scheduler/per-machine-independent.test.ts` (2) — a job WITHOUT the flag still yields on `hasRemoteClaim===true`; a `perMachineIndependent` job never consults the claim path and proceeds.
35
+ - `tests/unit/instar-settings-hooks.test.ts` + `tests/unit/migration-parity-hooks.test.ts` — updated for the new guard entry (registered in settings + accepted migrator-only gap, matching the pr-hand-lease-guard precedent).
36
+ - `node scripts/lint-model-registry-freshness.mjs` → PASS on the enriched manifest; `npx tsc --noEmit` clean; full `pnpm lint` chain PASS; `pnpm build` OK (the new job hashes into the built-in-job lockfile). End-to-end: the real `doorway-scan.md` installs via `installBuiltinJobs` with zero errors; its per-slug manifest carries `enabled:false` + `perMachineIndependent:true`.
37
+ - Deferred items (spec §Deferred DF1-DF6) remain tracked in `docs/specs/reports/DOORWAY-MODEL-KNOWLEDGE-REGISTRY-followups.md` (filed by inc1).
@@ -0,0 +1,96 @@
1
+ # Side-Effects Review — Doorway/Model Knowledge Registry, increment 2 (deterministic prober + scan-state + diff/debounce/breaker + dark job + §2.7 guard)
2
+
3
+ **Version / slug:** `doorway-model-registry-inc2`
4
+ **Date:** `2026-07-04`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `recommended` — this increment adds a real BLOCK authority (the §2.7 PreToolUse command-allowlist guard hook, which can `exit 2` and block a Bash call) and touches the `JobScheduler` claim/lease seam. See §1/§2/§4/§5.
7
+
8
+ ## Summary of the change
9
+
10
+ Second rollout increment of `DOORWAY-MODEL-KNOWLEDGE-REGISTRY-SPEC.md` (§Rollout step 2), landed DARK: the doorway-scan job ships `enabled:false` and nothing runs on a cadence until an operator enables it (step 4). Artifacts:
11
+
12
+ 1. **`scripts/doorway-scan.mjs`** — the deterministic prober (the security spine, §2.0). Owns all network I/O, secret handling, timeouts, size-caps, sanitization, scan-state read/write, diff/debounce/breaker, and the in-process `POST /view` + `POST /attention` delivery. Exports pure helpers for unit tests; `main()` runs only when invoked directly and is a harmless no-op stub in this dark increment.
13
+ 2. **Machine-local scan-state schema** (`.instar/state/doorway-scan.json`, §1.3) — created lazily; an EXHAUSTIVE `loadScanState()` read-clamp re-validates every field on every load + a coverage test.
14
+ 3. **Diff / debounce / breaker** (§2.3/§2.6) — asymmetric 2-scan debounce, a `consecutiveCompleteFailures` zero-doors breaker (retry-then-escalate, P22), tone-gate-safe attention body (raw diff → private view; never localhost/token link), and confirmed-delivery baseline advancement (no silent loss, no fresh-state flood).
15
+ 4. **Dark job template** (`src/scaffold/templates/jobs/instar/doorway-scan.md`, `enabled:false`, tier1/haiku, `toolAllowlist:["Bash"]`) + the new **`perMachineIndependent` job-def flag** threaded `JobDefinition` → `PerSlugManifest` → `buildPerSlugManifest` → `InstallBuiltinJobs` → `manifestToJobDefinition`, honored at `JobScheduler.triggerJob` (skips the global jobSlug claim/lease).
16
+ 5. **The §2.7 PreToolUse command-allowlist guard** (`doorway-scan-guard.js`) — installed via the always-overwrite `instar/` migration path + registered in `INSTAR_BASH_PRETOOLUSE_HOOKS`. A genuine stateful shell lexer allowing only the sanctioned prober invocation + a host-pinned localhost curl + read-only `test -f`/`cat`/`jq -r`.
17
+ 6. **Manifest enrichment** — additive per-door `probe{}` + `candidateDoorways[]` (backward-compatible; the freshness lint ignores them).
18
+
19
+ ## Decision-point inventory
20
+
21
+ - **`doorway-scan-guard.js` (NEW block authority)** — `add` — a PreToolUse Bash hook that can `exit 2` (block). SCOPE resolution fails OPEN; command matching fails CLOSED. Scoped by `INSTAR_JOB_SLUG==='doorway-scan'` (env-first, zero disk I/O off the hot path) → strict no-op in every other session.
22
+ - **`JobScheduler.triggerJob` claim/lease seam** — `modify` — a `perMachineIndependent` job now bypasses the claim check + claim-taking. Behavior for every job WITHOUT the flag is byte-identical (regression test).
23
+ - **The money gate (`meteredScopeGate`)** — `add` — a fail-closed predicate; refuses all metered spend on the scheduled/dark path. No metered completion is ever ISSUED in this increment (all test paths refuse before issuance).
24
+ - No message/dispatch/tone-gate decision point is touched.
25
+
26
+ ---
27
+
28
+ ## 1. Over-block
29
+
30
+ The §2.7 guard is a block authority, so over-block is the primary risk. Mitigations:
31
+ - **Tightly scoped:** the guard is an immediate no-op unless `INSTAR_JOB_SLUG==='doorway-scan'` (a scheduler-set env only the dark doorway-scan job carries). An instar-dev session, an interactive session, and every other job session hit the env-first fast path and ALLOW with zero disk I/O — proven by the subprocess test (a dangerous `cp` under another slug / no slug / a non-Bash tool all return exit 0).
32
+ - **Fails OPEN on scope:** any error resolving whether this is the doorway-scan session → ALLOW (exactly like the sibling `pr-hand-lease-guard.js`). A guard bug can never lock out an unrelated session's Bash. Proven (malformed stdin → exit 0).
33
+ - **Inside the doorway-scan session, over-block is intentional and bounded:** the guard fails CLOSED, but that session's only legitimate commands are the sanctioned shapes (the job body issues exactly `test -f …` and `node scripts/doorway-scan.mjs --scope free-probes`). A false-refuse there breaks the DARK scan loudly/safely, never widens scope — and the job is off by default, so the blast radius until step 4 is nil.
34
+ - The `enabled:false` job means the guard is never even reached on any agent until an operator opts in.
35
+
36
+ ## 2. Under-block
37
+
38
+ - **Guard under-block (letting a source write through):** the matcher is an ALLOWLIST with a genuine lexer — anything not provably ONE sanctioned simple command is REFUSED, so obfuscation (compounds, substitution, redirects, env-prefix, interpreters, heredocs, `curl -o`, non-localhost curl) is refused by construction, not by a denylist that can miss an idiom. The adversarial subprocess matrix asserts every such primitive REFUSED.
39
+ - **Money under-gate (a scheduled run spending):** quadruple-gated + a self-standing scheduled-session refusal that holds even if the guard fails open. `meteredScopeGate` refuses on free-scope / scheduled-session / no-marker / absent-or-nonpositive-budget / unknown-or-stale-price / over-cap — tested on every branch. No completion is issued on any refused path.
40
+ - **Scheduler under-block:** `perMachineIndependent` deliberately bypasses the claim — that is the intended behavior for a per-machine-idempotent, cheap, local-only scan (the misuse warning on the flag documents the boundary). No shared/canonical state is mutated on the cadence.
41
+
42
+ ## 3. Level-of-abstraction fit
43
+
44
+ Correct layers. The prober is a deterministic script (a detector that produces a signal + a maintainer diff), never a runtime authority over routing (canonical is authoritative — §1.1). The guard is a structural PreToolUse enforcer (Structure > Willpower) that replaces a prompt-level "don't edit source" wish. The `perMachineIndependent` flag lives on `JobDefinition` alongside its sibling `writesState`/`resumeOnReap` opt-in flags — the honest place for a per-job scheduling property.
45
+
46
+ ## 4. Signal vs authority compliance
47
+
48
+ **Required reference:** `docs/signal-vs-authority.md`
49
+
50
+ - The **prober + diff/breaker** are SIGNAL: they surface a maintainer diff / a deduped escalation to the operator; they never auto-apply to source (enforced structurally by the guard + `toolAllowlist`), never gate a message.
51
+ - The **§2.7 guard** is an AUTHORITY, but its veto rests on an OBJECTIVE, decidable shape check (a genuine shell-grammar parse to a single sanctioned simple-command), not brittle inference; it fails OPEN on scope (never blocks the wrong session) and CLOSED on match (a refused command breaks the dark scan safely). This is the same shape as the sibling `pr-hand-lease-guard.js` and the eli16/release-fragment gates: block carried by an objective binary, escape hatch is "run a sanctioned command."
52
+ - The **money gate** is an authority over spend, and it is fail-closed by construction (no spend without an explicit manual marker + positive cap + known/fresh price + non-scheduled session).
53
+
54
+ ---
55
+
56
+ ## 5. Interactions
57
+
58
+ - **Shadowing:** the guard is one more PreToolUse Bash entry appended to `INSTAR_BASH_PRETOOLUSE_HOOKS`; it does not replace or reorder the existing guards, and it is a no-op outside the doorway-scan slug, so it never shadows another guard's decision.
59
+ - **Double-fire:** none. The prober is the sole in-process POST actor; the job session runs no data-carrying curl. The scan job is off; when enabled it is `perMachineIndependent` (one run per machine, deliberately — never a duplicated cross-machine side-effect, since it only reads local disk + writes local scan-state).
60
+ - **Races:** the scan-state is a single machine-local file written atomically (temp + rename); no cross-process shared mutable state. `perMachineIndependent` removes the claim path entirely, so there is no claim race for this job.
61
+ - **Feedback loops:** none. The prober never edits source; the diff is a review artifact. The breaker retries on a widening backoff (never a tight loop) and escalates once (deduped).
62
+
63
+ ## 6. External surfaces
64
+
65
+ - **Other agents on the machine:** the guard fires fleet-wide on every Bash call but is a strict env-first no-op for all non-doorway-scan sessions (zero disk I/O on that path).
66
+ - **Install base:** the job template installs via `InstallBuiltinJobs` (non-destructive install-if-missing, on init + update). The guard hook installs via the always-overwrite `instar/` `migrateHooks()` path + `ensureInstarBashPreToolUseHooks` settings wiring — migrator-only for now (documented gap, matching pr-hand-lease-guard: fires only in the dark job's session, so inert until enabled; a fresh-init agent gets it on first auto-update, well before any operator enables the job). The prober + manifest enrichment ship as source (present on source-carrying agents; absent on pure end-user agents → the prober-presence gate makes the job a silent no-op).
67
+ - **External systems (Telegram/Slack/GitHub/Cloudflare):** none in this dark increment (the prober's `POST /attention`/`POST /view` are local + only reached when the job is enabled).
68
+ - **Persistent state:** `.instar/state/doorway-scan.json` (machine-local, created lazily on first scan, EXCLUDED from the backup manifest by design — never replicated onto another machine). No writes happen until the job is enabled.
69
+ - **Operator surface (Mobile-Complete Operator Actions):** none added in this increment (the `GET /doorways` read surface + CLAUDE.md awareness are increment 3). Not applicable.
70
+
71
+ ## 6b. Operator-surface quality
72
+
73
+ No operator-facing surface added — not applicable. (When the job is enabled in step 4, its only operator output is a jargon-safe attention item + a private view — both already designed tone-gate-safe.)
74
+
75
+ ---
76
+
77
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
78
+
79
+ - The **prober + manifest + guard + job template** are git-tracked instar source / built-in templates → byte-identical on every machine by construction. No divergence, no replication path.
80
+ - The **live scan-state** is machine-local BY DESIGN (a door's liveness is a physical fact of one disk — the identical posture of the Playwright Profile Registry). It is deliberately EXCLUDED from `BackupManager.includeFiles` — restoring one machine's disk-liveness onto another would be actively false. A `perMachineIndependent` job is the structural expression: each machine runs its own scan and writes its own scan-state; no lease elects a single machine.
81
+ - **One-voice / notices:** when enabled, each machine's diff uses a machine-qualified `sourceContext` (`doorway-scan:<machineId>`) so two machines' findings stay DISTINCT rows (never coalesced), and route to the alerts/hub topic (P23) — not a per-machine forum topic.
82
+
83
+ ---
84
+
85
+ ## 8. Rollback cost
86
+
87
+ DARK increment — **revert-and-patch, near-zero blast radius.**
88
+ - Hot-fix: `git revert` the PR. The guard hook is removed on the next migration (always-overwrite path re-writes the hook set; a settings entry pointing at a now-absent hook is a non-blocking error → Bash proceeds). The job template is retired by `InstallBuiltinJobs` when the template disappears (manifest flipped `enabled:false`+retired). The `perMachineIndependent` reverts cleanly (the flag simply disappears; jobs fall back to the claim path). The manifest `probe{}`/`candidateDoorways` are additive and the lint ignores them, so removing them is inert.
89
+ - Data migration: none — no scan-state is written while the job is off; a stale scan-state file is regenerable and self-healing (corrupt/old-schema → fresh, additive backfill).
90
+ - Agent state repair: none.
91
+ - User visibility: none — everything is off by default.
92
+ - Kill switch short of revert: the job stays `enabled:false`; the guard is inert outside the (disabled) doorway-scan session.
93
+
94
+ ## Second-pass review
95
+
96
+ Concur with the review. I independently read the guard hook string in `getDoorwayScanGuardHook()`, the `JobScheduler.triggerJob` staged diff, the `meteredScopeGate`/`main()`/lexer in `scripts/doorway-scan.mjs`, and all three test files. The fail-OPEN-on-scope / fail-CLOSED-on-match claim is TRUE in the code: Region A returns `exit 0` for a non-Bash tool, a non-string command, any JSON-parse error, and — the load-bearing check — any session where `INSTAR_JOB_SLUG !== 'doorway-scan'` (plus an 8s fail-open stdin backstop), so an instar-dev/interactive/other-job session can never be over-blocked; Region B (only reachable inside the doorway-scan session) blocks with `exit 2` on any non-sanctioned command AND on a thrown lexer error. `lexSimpleCommand` is a genuine stateful char-by-char tokenizer (single/double-quote spans, backslash escapes, quote-state tracking — not a substring/regex scan) that refuses every operator/redirection/expansion/substitution/newline and the leading `NAME=` env-prefix; I could not find a source-write or metered-spend primitive that slips through (`node` is admitted only for the exact prober argv, so `node -e` is refused; curl is host-pinned to localhost with output-redirect and non-allowlisted flags refused), and the adversarial subprocess matrix drives the real deployed hook and asserts both directions. The `perMachineIndependent` bypass leaves the no-flag path byte-identical (the flag is falsy for normal jobs, so `claimPath`, `remoteHeld`, and the claim-taking `if/else-if/else` all reduce to the original expressions; the regression test confirms a no-flag job still yields on a remote claim while the pmi job never consults the claim path). The money gate is genuinely fail-closed: the self-standing scheduled-session refusal precedes the marker check, both defaults (`manualMarker=false`, `isScheduledSession=false`) still refuse, `main()` issues no completion and forces `free-probes` on the scheduled/dark path, and every refusal branch is unit-tested. (Note: `grep` for the literal `meteredScopeGate` intermittently returned nothing in this environment, but Read + `sed` both show the full branch-by-branch test suite at lines 223-254 — a tooling quirk, not a missing test.)