@redue/cli 0.1.0-alpha.5 → 0.1.0-alpha.7

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/CHANGELOG.md CHANGED
@@ -1,6 +1,30 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.0-alpha.5 — unreleased candidate
3
+ ## 0.1.0-alpha.7 — 2026-10-07
4
+
5
+ - Guide explicitly configured direct-compiler reuse through one caller-aware
6
+ synchronized query; skip reuse queries when fresh execution is already required.
7
+ - Update intact project-local agent instructions safely; installation alone does
8
+ not update them. See the [existing-user procedure](docs/user/agents.md#update-an-existing-connection).
9
+ - In the finite Codex/macOS comparison, revised guidance reached eligible evidence
10
+ and reused it in 4/4 opportunities versus 1/4; verification tool calls fell
11
+ from 28 to 14. Evidence semantics are unchanged. No universal compliance,
12
+ tail-latency or causal agent-task speedup is claimed. Claude behavior and
13
+ cross-host handoff remain untested.
14
+
15
+ ## 0.1.0-alpha.6 — 2026-10-07
16
+
17
+ - Reuse guarded compiler discovery for warm direct-TypeScript decisions on macOS
18
+ with healthy historical observation. Membership, absence, compiler bytes,
19
+ caller context and receipt selection remain validated independently.
20
+ - Keep full discovery/reconciliation fallbacks and existing platform boundaries.
21
+ - On one measured project, complete warm decision median fell from roughly
22
+ 791 to 474 ms. This is not a universal speedup, agent-task acceleration,
23
+ memory improvement, or a faster relevant-edit/rebuild claim.
24
+ - Stop observers before upgrading and restart with the same config/state.
25
+ Preserve receipts; no configuration migration is performed.
26
+
27
+ ## 0.1.0-alpha.5 — 2026-10-07
4
28
 
5
29
  - Add opt-in project-local agent activation and direct TypeScript evidence, with
6
30
  fresh-session reuse demonstrated in the tested Codex/macOS configuration.
@@ -3,18 +3,27 @@
3
3
  Use the CLI with explicit project-local activation below, or add this policy to a
4
4
  generic agent's instructions with the project owner's permission.
5
5
 
6
- 1. Query ordinary `redue status --json` first.
7
- 2. Require schema 1 and healthy observation. If a check has `freshness: "CURRENT"`, `result: "PASS"`, and
8
- `reuse_eligible: true`, reuse it. Missing eligibility means false. Do not rerun merely for freshness.
9
- 3. If STALE, run `redue run CHECK` when that verification is needed.
10
- 4. If the historical `result` is FAIL, report it honestly. The human CLI calls
11
- this FAILED; applicability is a separate field, never an excuse to call it green.
12
- 5. If UNVERIFIED, inspect its reason. Use `status --sync --json` when establishing
13
- applicability is worthwhile, or rerun the underlying check when cheaper/simpler.
14
- Use `redue run CHECK` if a new receipt is wanted. Incomplete coverage can remain
15
- UNVERIFIED even after success.
16
- 6. Do not assume synchronization is cheaper than execution. There are no universal
17
- package-manager or OS thresholds. Inexpensive checks may be cheaper to rerun.
6
+ 1. If fresh execution is already required, execute it; do not synchronize merely
7
+ to confirm an irrelevant reuse opportunity. Use `redue run CHECK` when recording
8
+ the configured check. An explicit npm-script request still requires npm.
9
+ 2. For reuse of the explicitly configured direct compiler recipe
10
+ (`qualification: "npm-typescript-direct-v1"`, direct compiler command; receipt
11
+ `verification_recipe: "npm-direct-typescript@1"`), query once:
12
+ `redue --config CONFIG status --sync --json`. Do not infer a recipe from its name.
13
+ Cached status cannot establish its caller context. In mixed configurations,
14
+ assess each check's contract separately; this is not a global sync-first policy.
15
+ 3. For other/unknown recipes, query ordinary `status --json` first and decide whether
16
+ synchronization is worthwhile. Ordinary status and `--short` remain cheap inspection.
17
+ 4. Reuse only schema 1, healthy observation and the selected check's CURRENT/PASS
18
+ with `reuse_eligible: true`. Missing eligibility means false. A historical FAIL
19
+ is never green evidence. Do not rerun eligible evidence merely for freshness.
20
+ 5. STALE requires execution when verification is needed. UNVERIFIED requires an
21
+ honest choice of synchronization, permitted execution or reporting the limitation.
22
+ Recording-only success can remain UNVERIFIED. A deliberate cheap-check rerun is
23
+ permitted; there are no universal platform/package-manager timing thresholds.
24
+ 6. Use a successful synchronized response for the current decision without repeated
25
+ sync/explain calls unless edits, context changes or missing information require it.
26
+ Do not loop until green or use an earlier response after a later query fails.
18
27
 
19
28
  `redue explain CHECK --json` gives synchronized detail, including a known changed
20
29
  input when available. It has the same cost caveat as synchronized status.
@@ -37,9 +46,9 @@ CURRENT. Do not delete state or reset receipts to make the output green.
37
46
  REDUE tells you what evidence exists and whether it still applies. It does not
38
47
  promise that establishing applicability is cheaper than every possible check.
39
48
 
40
- ## Connect once (candidate)
49
+ ## Connect once (experimental)
41
50
 
42
- The alpha.5 candidate supports opt-in project-local activation and direct
51
+ Alpha.5 and later support opt-in project-local activation and direct
43
52
  TypeScript evidence, with fresh-session reuse demonstrated for Codex CLI 0.160.1
44
53
  on macOS under the documented owner-approved checkout/state/socket route. CLI
45
54
  mechanics are tested on macOS/Linux/Windows; model behavior is not claimed for
@@ -47,7 +56,7 @@ every host/platform or sandbox policy. Claude setup/removal mechanics are tested
47
56
  but Claude behavior and cross-host reuse remain untested. Reuse is not guaranteed,
48
57
  and the existing subsecond example establishes no meaningful net time saving.
49
58
 
50
- After installing this candidate and running `redue init` in a trusted project:
59
+ After installing REDUE and running `redue init` in a trusted project:
51
60
 
52
61
  ```sh
53
62
  redue agent setup codex --dry-run
@@ -62,6 +71,42 @@ setup requires `--apply`. `--dry-run` never writes. Installing REDUE or running
62
71
  `init` does not connect an agent automatically. Setup does not start an observer
63
72
  or execute verification. Use `redue start` when you want observation.
64
73
 
74
+ ## Update an existing connection
75
+
76
+ Installing a newer package does **not** rewrite project instructions. From the
77
+ same project and installation, stop every observer using that installation before
78
+ replacing it. Keep the original config and state selection; do not create a second
79
+ config or delete receipts. Example for a default npm installation/config:
80
+
81
+ ```sh
82
+ redue --config redue.config.json stop
83
+ npm install --global @redue/cli@alpha
84
+ redue --config redue.config.json agent setup codex --dry-run
85
+ redue --config redue.config.json agent setup codex --apply
86
+ redue --config redue.config.json start
87
+ redue --config redue.config.json status --sync --json
88
+ ```
89
+
90
+ Use the **same `--prefix`** as the existing npm installation when it has a custom
91
+ prefix, and invoke its corresponding `redue` binary. Keep your actual config path
92
+ on every command. For Claude, select `claude` in both setup commands. Preserve any
93
+ existing `--state-dir` on lifecycle/status commands; setup does not embed custom
94
+ state locations, so retain the documented generic instruction path if your agent
95
+ workflow needs an explicit custom state. PowerShell may use the existing
96
+ `npm.cmd`/`redue.cmd` shims without changing script policy.
97
+
98
+ Review the preview before applying. Intact owned content updates; unrelated text
99
+ is preserved. Edited managed content is refused, not overwritten. If refused,
100
+ review/reconcile those edits deliberately; do not remove ownership records to force
101
+ an update. Restart the observer with its original selection even if you postpone
102
+ updating instructions, then start a **fresh agent session** after a successful
103
+ update. Setup creates no verification receipt.
104
+
105
+ Managed project metadata can itself be an observed input. Prior receipts remain
106
+ historical evidence, but setup may make them STALE or UNVERIFIED. Reassess normally
107
+ and run required checks; an upgrade is not a promise that existing receipts remain
108
+ CURRENT. Do not suppress that invalidation or reset state.
109
+
65
110
  Codex gets a short managed section in project `AGENTS.md` and
66
111
  `.agents/skills/redue-verification/SKILL.md`. Claude Code gets a managed section
67
112
  in `CLAUDE.md` and `.claude/skills/redue-verification/SKILL.md`. Each uses the same
@@ -107,7 +152,7 @@ proven installation mismatch.
107
152
  Codex CLI 0.160.1 on macOS can use the approved exact observer-socket route while
108
153
  other sockets and direct network access remain denied. Its independent sessions
109
154
  receive different proxy values. Ordinary npm invocation is **not** equivalent
110
- across arbitrary notifier state: this candidate withholds automatic npm-launcher
155
+ across arbitrary notifier state: REDUE withholds automatic npm-launcher
111
156
  reuse. Do not change proxy settings or permissions to get green evidence.
112
157
 
113
158
  The separate opt-in [direct local compiler recipe](direct-typescript.md) does not
@@ -124,7 +169,7 @@ performs the same bounded, read-only decision check and exits 2 when reuse is no
124
169
  eligible. It neither executes verification nor grants access; synchronization
125
170
  may run configured probes. A successful outside-shell result is insufficient.
126
171
 
127
- Use the same setup command after a candidate upgrade; it updates intact managed
172
+ Use the same setup command after an upgrade; it updates intact managed
128
173
  content idempotently. Changed skill text or a changed managed block causes a
129
174
  conflict instead of an overwrite. Review your edits before retrying.
130
175
 
@@ -154,8 +199,8 @@ paths. Other worktrees and remote containers have separate local evidence.
154
199
 
155
200
  For another agent, put a short instruction in its documented project mechanism:
156
201
  “Before deciding whether configured verification needs repeating, read REDUE's
157
- agent policy, query ordinary `redue status --json` for the intended config, and
158
- apply the schema/health/reuse rules.” Supply this document and the
202
+ agent policy, identify the selected recipe and use its decision query for the intended config.
203
+ Apply the schema/health/reuse rules.” Supply this document and the
159
204
  [JSON contract](json.md) as the detailed policy. Review the agent's actual tool
160
205
  trace before calling activation verified. A local npm installation does not install
161
206
  REDUE in a remote container; that environment needs its own CLI, checkout,
@@ -57,11 +57,13 @@ prefix. Uninstalling the package does not remove local verification evidence;
57
57
  `redue remove-state` is a separate, explicit action. Alpha.1 remains available as
58
58
  historical software and is superseded for package identity, not a runtime defect.
59
59
 
60
- ## Upgrading to alpha.4
60
+ ## Upgrading REDUE
61
61
 
62
62
  Stop every observer using the installation before upgrading, then restart each
63
63
  with the same config/state selection. See [the npm launcher notice](npm-launcher-notice.md)
64
- for exact commands. Do not remove state or delete receipts.
64
+ for exact commands. Do not remove state or delete receipts. Existing agent connections require a separate
65
+ [preview/apply policy update](agents.md#update-an-existing-connection); installing
66
+ the package alone does not update project instructions.
65
67
 
66
68
  ## First project
67
69
 
@@ -90,7 +92,7 @@ reusability. Qualification can change when configuration or installation changes
90
92
 
91
93
  `run` streams normal output and preserves execution environment and cancellation.
92
94
  For the narrow pnpm TypeScript contract, `init` explicitly shows the direct installed
93
- compiler invocation it selects. In this candidate, npm scripts remain recording-only;
95
+ compiler invocation it selects. Npm scripts remain recording-only;
94
96
  use `redue init --recipe typescript-direct --dry-run` to preview a distinct,
95
97
  explicit [local compiler recipe](direct-typescript.md). Other scripts run through
96
98
  the package manager. No existing configuration is silently migrated.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@redue/cli",
3
3
  "private": false,
4
- "version": "0.1.0-alpha.5",
4
+ "version": "0.1.0-alpha.7",
5
5
  "publishConfig": {
6
6
  "access": "public",
7
7
  "tag": "alpha"
@@ -45,15 +45,19 @@ function saved(root,host){const text=read(root,marker(host));if(text===null)retu
45
45
  const value=JSON.parse(text),h=hosts[host];
46
46
  if(value.schema!==1||value.host!==host||value.instruction!==h.instruction||value.skill!==h.skill||
47
47
  typeof value.config!=='string'||path.isAbsolute(value.config)||value.config.includes('/')||value.config.includes('\\')||
48
- value.block!==blockFor(host,value.config)||
48
+ (value.block!==blockFor(host,value.config)&&value.block!==blockFor(host,value.config,true))||
49
49
  typeof value.skillHash!=='string'||typeof value.instructionCreated!=='boolean')
50
50
  throw Error('unrecognized agent ownership record; inspect it without deleting user content');
51
51
  return value;
52
52
  }
53
- function blockFor(host,relative){return `\n\n<!-- REDUE:${host}:begin -->\n`+
53
+ // Accept only these exact owned template versions; never arbitrary stored text.
54
+ function blockFor(host,relative,legacy=false){return `\n\n<!-- REDUE:${host}:begin -->\n`+
54
55
  `Before deciding whether configured verification needs repeating, use REDUE.\n`+
55
- `Read ${hosts[host].skill} for the decision policy, then query ordinary status\n`+
56
- `before choosing reuse or execution. Only healthy schema-1 CURRENT/PASS with\n`+
56
+ (legacy?`Read ${hosts[host].skill} for the decision policy, then query ordinary status\n`+
57
+ `before choosing reuse or execution. Only healthy schema-1 CURRENT/PASS with\n`:
58
+ `Read ${hosts[host].skill}: verify the selected recipe, use caller-aware sync\n`+
59
+ `for direct-compiler reuse, and execute fresh obligations without a reuse query.\n`+
60
+ `Only healthy schema-1 CURRENT/PASS with\n`)+
57
61
  `explicit reuse_eligible=true permits reuse; honor fresh-run requests and other\n`+
58
62
  `project verification obligations. Reassess after edits or failed queries.\n`+
59
63
  `Selected config (JSON path relative to this file's directory): ${JSON.stringify(relative)}.\n`+
@@ -63,7 +67,7 @@ function blockFor(host,relative){return `\n\n<!-- REDUE:${host}:begin -->\n`+
63
67
  function skillFor(relative){return policy+`\nSelected configuration\n\n`+
64
68
  `The configuration path, relative to the project instruction file's directory, is\n`+
65
69
  `${JSON.stringify(relative)} (a JSON string, not shell code). From that directory use\n`+
66
- `\`redue --config CONFIG status --json\`, replacing CONFIG with that single quoted\n`+
70
+ `the policy-selected \`redue --config CONFIG ...\` command, replacing CONFIG with one quoted\n`+
67
71
  `path argument. Apply the same --config to run, explain, start and stop. From a\n`+
68
72
  `nested directory use the corresponding relative config path. Do not guess among\n`+
69
73
  `multiple configs. Receipts are local to the selected checkout/configuration.\n`;}
@@ -3,11 +3,30 @@ name: redue-verification
3
3
  description: Decide whether configured verification needs running, using REDUE evidence before verification and completion decisions.
4
4
  ---
5
5
 
6
- Query ordinary `redue status --json` before deciding whether configured checks need
7
- repeating. Apply the selected configuration described below to every command.
8
- Recheck at the decision point after edits; an earlier CURRENT is not continuing
9
- authorization after changes, context changes, observation loss, or a failed query.
10
- There is no atomic guarantee against concurrent edits.
6
+ Apply the selected configuration below to every command. First distinguish an
7
+ execution obligation from a reuse decision:
8
+
9
+ - If fresh execution is explicitly requested or independently required, execute
10
+ it without a reuse query that cannot change that decision. Use `redue run CHECK`
11
+ to record the configured check. An explicit npm-script request requires npm,
12
+ not a direct compiler substitute.
13
+ - To evaluate reuse for a check whose selected config has
14
+ `qualification: "npm-typescript-direct-v1"` and the direct compiler command
15
+ (or whose receipt metadata has `verification_recipe: "npm-direct-typescript@1"`
16
+ consistent with that config), use one `redue --config CONFIG status --sync --json`
17
+ directly. Cached status cannot establish this recipe's caller eligibility.
18
+ Do not infer the recipe from a check's name or precede sync with cached status
19
+ solely as a ritual. Evaluate each check separately in mixed configurations.
20
+ - For other or unidentified recipes, start with ordinary `status --json` and
21
+ choose sync or execution from its evidence. Cached status and `--short` remain
22
+ cheap inspection, not caller-aware proof. A deliberate inexpensive rerun is
23
+ still permitted; there are no universal OS/package-manager cost thresholds.
24
+
25
+ Use one successful, still-applicable synchronized response for the current
26
+ decision; do not repeat sync/explain without edits, context changes or missing
27
+ information. Do not loop until green. Reassess after changes, observation loss or
28
+ a later failed query; earlier CURRENT is not continuing authority. There is no
29
+ time-based authorization or atomic guarantee against concurrent edits.
11
30
 
12
31
  Read schema 1 JSON. Reuse only when the read succeeds, `schema` is 1,
13
32
  `observation.healthy` is true, and the selected check has all three:
@@ -29,8 +48,7 @@ checks merely for freshness.
29
48
  lifecycle scripts. Inspect the configuration/verification_recipe before claiming
30
49
  what ran. An explicit request for `npm run typecheck` requires that npm invocation;
31
50
  direct compiler evidence does not satisfy it.
32
- - Explicit user requests for fresh execution and independent task-specific
33
- verification obligations still apply. REDUE covers only configured checks.
51
+ - REDUE covers only configured checks, not every task-specific verification obligation.
34
52
 
35
53
  Use `redue explain CHECK --json` for synchronized detail (also potentially costly).
36
54
  `changed_inputs` can be incomplete; an empty list does not establish no changes.
package/src/daemon.mjs CHANGED
@@ -16,7 +16,7 @@ import {permittedRoots} from './installed-inputs.mjs';
16
16
  import {assertOwnedStatePlacement} from './owned-state.mjs';
17
17
  import {atomicJson,readReceipts,readReceiptSnapshot,receiptRevision} from './state-store.mjs';
18
18
  import {history} from './observation.mjs';
19
- import {inputKey} from './decision-validation.mjs';
19
+ import {inputKey,planGuard} from './decision-validation.mjs';
20
20
  import {timing} from './decision-profile.mjs';
21
21
 
22
22
  const configFile=path.resolve(process.argv[2]);
@@ -110,6 +110,11 @@ function persistedPlan(bundle) {
110
110
  return [name,safe];
111
111
  }))};
112
112
  }
113
+ function macProbeBarrier() {
114
+ return process.platform==='darwin'&&hasHistory&&!historyDisabled&&healthy&&
115
+ watcher&&!planning&&!recovering&&!planDirty&&!index?.unavailable&&
116
+ !pending.size&&!pendingChecks.size&&!external.size&&verifyRoot();
117
+ }
113
118
  function probe(row,allowReuse=false) {
114
119
  const started=performance.now();
115
120
  const supported=new Map([
@@ -118,24 +123,45 @@ function probe(row,allowReuse=false) {
118
123
  ['yarn-workspace-tsc-v1','yarn-workspace-typecheck-probe.mjs'],
119
124
  ['pnpm-tsc-v1','pnpm-typecheck-probe.mjs']]);
120
125
  const expected=supported.get(row.plan.qualification);
121
- const cacheable=process.platform==='win32'&&row.plan.probes.length===1&&expected&&
126
+ const macDirect=process.platform==='darwin'&&row.plan.qualification==='npm-typescript-direct-v1';
127
+ const cacheable=(process.platform==='win32'||macDirect)&&row.plan.probes.length===1&&expected&&
122
128
  row.plan.probes[0][1]===fileURLToPath(new URL(expected,import.meta.url));
123
129
  const key=inputKey(row);
124
- if(cacheable&&allowReuse&&row.probeCache?.input===key){
130
+ let guard=null,guardReason=null;
131
+ if(macDirect&&cacheable)try{guard=planGuard(index.bundle);}catch{guardReason='plan_guard_unavailable';}
132
+ const cache=row.probeCache;
133
+ const macGuarded=!macDirect||(macProbeBarrier()&&!row.plan.unresolved.length&&
134
+ guard!==null&&cache?.schema===1&&cache.guard===guard&&
135
+ Array.isArray(cache.queries)&&cache.queries.length>0);
136
+ const forceFull=process.env.VSTATE_TEST_FAULTS==='1'&&process.env.VSTATE_TEST_FULL_PROBES==='1';
137
+ if(cacheable&&allowReuse&&macGuarded&&!forceFull&&cache?.input===key){
125
138
  const argv=row.plan.probes[0];
126
- const out=spawnSync(argv[0],[...argv.slice(1),'--redue-context'],{
127
- cwd:row.plan.cwd,timeout:10000,maxBuffer:1024*1024,stdio:['ignore','pipe','pipe']});
139
+ const out=spawnSync(argv[0],[...argv.slice(1),macDirect?'--redue-validate-queries':'--redue-context'],{
140
+ cwd:row.plan.cwd,timeout:10000,maxBuffer:1024*1024,
141
+ ...(macDirect?{input:JSON.stringify({schema:cache.schema,context:cache.context,queries:cache.queries})}:
142
+ {stdio:['ignore','pipe','pipe']})});
143
+ metrics.probeContextProcesses=(metrics.probeContextProcesses||0)+1;
144
+ let stillValid=!macDirect;
145
+ if(macDirect)try{stillValid=macProbeBarrier()&&inputKey(row)===key&&planGuard(index.bundle)===guard;}catch{}
128
146
  if(!out.error&&!out.signal&&out.status===0&&
129
- out.stdout.toString()===row.probeCache.context){
147
+ out.stdout.toString()===cache.context&&stillValid){
130
148
  row.probeHash=row.probeCache.hash;row.probeAt=Date.now();row.probeError=null;
131
149
  metrics.probeReuses=(metrics.probeReuses||0)+1;
132
150
  timing('daemon.probe_context_validation',started,{outcome:'reused',reason:'content_and_context_unchanged'});
133
151
  return;
134
152
  }
153
+ guardReason='context_or_compiler_queries_changed';
135
154
  timing('daemon.probe_context_validation',started,{outcome:'changed_or_unavailable'});
136
155
  }
137
- timing('daemon.probe_reason',started,{reason:!cacheable?'generic_probe':
138
- !row.probeCache?'no_validated_result':row.probeCache.input!==key?'inputs_changed':'context_changed'});
156
+ if(macDirect&&cache?.guard&&guard!==cache.guard)planDirty=true;
157
+ const fullReason=forceFull?'forced_full_reference':guardReason||
158
+ (macDirect&&!macGuarded?'mac_validation_basis_unavailable':!cacheable?'generic_probe':
159
+ !row.probeCache?'no_validated_result':row.probeCache.input!==key?'inputs_changed':'context_changed');
160
+ timing('daemon.probe_reason',started,{reason:fullReason});
161
+ metrics.fullProbeReasons??={};metrics.fullProbeReasons[fullReason]=(metrics.fullProbeReasons[fullReason]||0)+1;
162
+ metrics.fullProbes=(metrics.fullProbes||0)+1;
163
+ // A failed or incomplete full probe must not leave an older certificate usable.
164
+ if(macDirect)row.probeCache=null;
139
165
  const values=[];let checkpoint=null;
140
166
  for(const argv of row.plan.probes) {
141
167
  // Supported TypeScript probes perform bounded input/toolchain hashing.
@@ -207,7 +233,7 @@ function probe(row,allowReuse=false) {
207
233
  row.revision++;row.lastReason='declared state probe changed or recovered';}
208
234
  row.probeHash=value;row.probeAt=Date.now();row.probeError=null;
209
235
  if(checkpoint)row.probeCache=checkpoint.queries?{input:key,context:checkpoint.context,
210
- hash:value,queries:checkpoint.queries}:null;
236
+ hash:value,queries:checkpoint.queries,...(macDirect?{schema:1,guard}: {})}:null;
211
237
  timing('daemon.probe',started,{outcome:'ok',commands:values.length,
212
238
  reusable:row.probeCache?1:0,queries:row.probeCache?.queries.length||0});
213
239
  }
@@ -820,7 +846,7 @@ async function request(message) {
820
846
  timing('daemon.decision_reason',totalStarted,{decision,action:message.action,
821
847
  plan:process.platform==='win32'?'validate_dependencies_before_reuse':'existing_plan',
822
848
  probe:'reuse_only_with_identical_content_and_context',installed:'current_content_required',
823
- phase:metrics.planPhase||'initializing',pending:pending.size,
849
+ planPhase:metrics.planPhase||'initializing',pending:pending.size,
824
850
  notifications:metrics.notifications,planRebuilds:metrics.planRebuilds});
825
851
  if(process.platform==='win32'&&process.env.VSTATE_TEST_FAULTS==='1'&&
826
852
  process.env.VSTATE_TEST_WINDOWS_GAP_FILE&&
@@ -872,8 +898,18 @@ async function request(message) {
872
898
  }catch(e){installedRecheck=null;observationGap('installed_recheck',e);}
873
899
  }
874
900
  const probeStarted=performance.now();
875
- if(index&&healthy&&!validatedColdIndex&&message.action!=='prelaunch')
876
- for(const row of index.checks.values())probe(row);
901
+ if(index&&healthy&&!validatedColdIndex&&message.action!=='prelaunch'){
902
+ const macReuse=macProbeBarrier();
903
+ for(const row of index.checks.values())probe(row,macReuse);
904
+ if(macReuse&&process.env.VSTATE_TEST_FAULTS==='1'&&process.env.VSTATE_TEST_MAC_PROBE_SIGNAL){
905
+ fs.writeFileSync(process.env.VSTATE_TEST_MAC_PROBE_SIGNAL,'validated');
906
+ await new Promise(resolve=>setTimeout(resolve,200));
907
+ }
908
+ // Child validation blocks JS event delivery. Finish with another continuity
909
+ // barrier so queued edits/gaps cannot be published using the pre-probe index.
910
+ if(macReuse)try{await catchUp();if(planDirty||pending.size||flushTimer)flush();}
911
+ catch(e){observationGap('post_probe_history',e);}
912
+ }
877
913
  timing('daemon.request_probes',probeStarted,{checks:index?.checks.size||0});
878
914
  lastObservation=Date.now();publish();
879
915
  const applicabilityStarted=performance.now();
@@ -1002,7 +1038,12 @@ const probeTimer=setInterval(async()=>{
1002
1038
  // but can block control requests behind several seconds of child work.
1003
1039
  if(process.platform==='win32')return;
1004
1040
  if(healthy&&index&&!planning&&!decisionRequests){
1005
- try{await catchUp();for(const row of index.checks.values())probe(row);publish();}
1041
+ try{await catchUp();
1042
+ if(healthy&&!planning&&!recovering){const macReuse=macProbeBarrier();
1043
+ for(const row of index.checks.values())probe(row,macReuse);
1044
+ if(macReuse){await catchUp();if(planDirty||pending.size||flushTimer)flush();}
1045
+ }
1046
+ publish();}
1006
1047
  catch(e){observationGap('periodic_query',e);}
1007
1048
  }},5000);
1008
1049
  process.on('uncaughtExceptionMonitor',error=>{
@@ -4,15 +4,42 @@ import {directProject,directInterpretation} from './direct-typescript.mjs';
4
4
  import {typeScriptInputFacts} from './typescript-input-facts.mjs';
5
5
  import {contextOnly,probeResult} from './probe-result.mjs';
6
6
  import {realObservedPath} from './path-identity.mjs';
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import {queriesMatch} from './typescript-list.mjs';
10
+ import {timing} from './decision-profile.mjs';
7
11
  const hash=value=>createHash('sha256').update(JSON.stringify(value)).digest('hex');
8
12
  try{
9
13
  const [rootArg,script]=process.argv.slice(2),root=realObservedPath(rootArg);
10
14
  // Verify all installed implementation bytes before loading compiler API/code,
11
15
  // including the optimized context-only path. A version label is insufficient.
16
+ const implementationStarted=performance.now();
12
17
  const compiler=directProject(root,script);
18
+ timing('direct_probe.implementation',implementationStarted,{version:compiler.version});
13
19
  const context={interpretation:directInterpretation,compiler:compiler.digest,
14
20
  entry:compiler.entry,node:[realObservedPath(process.execPath),process.version]};
15
21
  const contextHash=hash(context);
22
+ if(process.argv.includes('--redue-validate-queries')){
23
+ // Internal, process-local discovery certificate; never a verification receipt.
24
+ // Verify the current implementation above before queryAnswer can load its API.
25
+ const started=performance.now(),raw=fs.readFileSync(0,'utf8');
26
+ if(Buffer.byteLength(raw)>8*1024*1024)throw Error('certificate too large');
27
+ const certificate=JSON.parse(raw),kinds=new Set(['readDirectory','readFile',
28
+ 'fileExists','directoryExists','realpath','directories','entries']);
29
+ if(certificate.schema!==1||certificate.context!==contextHash||
30
+ !Array.isArray(certificate.queries)||!certificate.queries.length||
31
+ certificate.queries.some(q=>!Array.isArray(q)||q.length<3||q.length>4||
32
+ !kinds.has(q[0])||typeof q[1]!=='string'||!path.isAbsolute(q[1])||
33
+ q[0]==='readDirectory'&&(q[3]?.tsRoot!==compiler.tsRoot||!Array.isArray(q[3]?.args)||
34
+ q[3]?.argsVersion!==1||!Array.isArray(q[3]?.undefinedArguments)||
35
+ q[3].undefinedArguments.some(i=>!Number.isInteger(i)||i<0||i>=q[3].args.length))))
36
+ throw Error('certificate unavailable');
37
+ const matched=queriesMatch(certificate.queries);
38
+ timing('direct_probe.query_validation',started,{queries:certificate.queries.length,matched:matched?1:0});
39
+ if(!matched)throw Error('compiler queries changed');
40
+ process.stdout.write(contextHash);
41
+ process.exit(0);
42
+ }
16
43
  if(!contextOnly(contextHash)){
17
44
  const {queries,...inputs}=typeScriptInputFacts(root,compiler.tsRoot,compiler.entry);
18
45
  probeResult(hash({root,script,...inputs,...context}),contextHash,queries);
@@ -9,7 +9,8 @@ const require=createRequire(import.meta.url);
9
9
  const hash=value=>createHash('sha256').update(JSON.stringify(value)).digest('hex');
10
10
  export function queryAnswer(kind,file,options) {
11
11
  try{
12
- if(kind==='readDirectory')return require(options.tsRoot).sys.readDirectory(file,...options.args).sort();
12
+ if(kind==='readDirectory')return require(options.tsRoot).sys.readDirectory(file,
13
+ ...options.args.map((value,i)=>options.undefinedArguments?.includes(i)?undefined:value)).sort();
13
14
  if(kind==='readFile')return hash(fs.readFileSync(file,'utf8'));
14
15
  if(kind==='fileExists')return fs.statSync(file).isFile();
15
16
  if(kind==='directoryExists')return fs.statSync(file).isDirectory();
@@ -42,7 +43,10 @@ export function compilerFiles(tsRoot,cwd) {
42
43
  const getDirectories=file=>{remember('directories',file);return ts.sys.getDirectories(file);};
43
44
  const readDirectory=(dir,...args)=>{
44
45
  const result=ts.sys.readDirectory(dir,...args);
45
- const full=path.resolve(dir),options={tsRoot,args};
46
+ // JSON turns undefined array entries into null. TypeScript distinguishes
47
+ // them (an omitted exclude list is not a null list), so retain that fact.
48
+ const full=path.resolve(dir),options={tsRoot,args,argsVersion:1,
49
+ undefinedArguments:args.flatMap((value,i)=>value===undefined?[i]:[])};
46
50
  queries.set('readDirectory\0'+full+JSON.stringify(args),
47
51
  ['readDirectory',full,[...result].sort(),options]);
48
52
  return result;
@@ -4,8 +4,9 @@ import path from 'node:path';
4
4
  import {realObservedPath} from './path-identity.mjs';
5
5
 
6
6
  // This is a machine-enforced contract, not a claim inferred from a script name.
7
- // The read-only runtime probe separately checks the effective TypeScript file
8
- // list, config graph, npm toolchain and caller context on every sync/run.
7
+ // Runtime validation checks effective compiler membership, configuration and
8
+ // toolchain identity. Supported discovery certificates still validate those
9
+ // filesystem queries; caller context is independently compared at the decision.
9
10
  export function qualifyTypeScript(root,selected,discovered={}) {
10
11
  if(selected.qualification===directQualification){
11
12
  const issues=[];