@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 +25 -1
- package/docs/user/agents.md +64 -19
- package/docs/user/getting-started.md +5 -3
- package/package.json +1 -1
- package/src/agent-integration.mjs +9 -5
- package/src/agent-policy.md +25 -7
- package/src/daemon.mjs +54 -13
- package/src/direct-typescript-probe.mjs +27 -0
- package/src/typescript-list.mjs +6 -2
- package/src/typescript-qualification.mjs +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.0-alpha.
|
|
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.
|
package/docs/user/agents.md
CHANGED
|
@@ -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.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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 (
|
|
49
|
+
## Connect once (experimental)
|
|
41
50
|
|
|
42
|
-
|
|
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
|
|
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:
|
|
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
|
|
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,
|
|
158
|
-
|
|
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
|
|
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.
|
|
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
|
@@ -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
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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`;}
|
package/src/agent-policy.md
CHANGED
|
@@ -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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
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()===
|
|
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
|
-
|
|
138
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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();
|
|
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);
|
package/src/typescript-list.mjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
//
|
|
8
|
-
//
|
|
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=[];
|