agentic-workflow-manager 9.8.0 → 9.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/commands/job/gate.js +18 -0
- package/dist/src/commands/job/index.js +61 -0
- package/dist/src/commands/model-policy/index.js +94 -0
- package/dist/src/commands/plan/index.js +59 -2
- package/dist/src/commands/watch/apply.js +32 -0
- package/dist/src/core/admission/index.js +52 -2
- package/dist/src/core/admission/registry-contracts.js +1 -1
- package/dist/src/core/journal/requests.js +1 -1
- package/dist/src/core/journal/types.js +22 -1
- package/dist/src/core/model-policy/canonical.js +18 -0
- package/dist/src/core/model-policy/capabilities.js +197 -0
- package/dist/src/core/model-policy/capability-types.js +2 -0
- package/dist/src/core/model-policy/digest.js +35 -0
- package/dist/src/core/model-policy/journal.js +124 -0
- package/dist/src/core/model-policy/paths.js +12 -0
- package/dist/src/core/model-policy/resolve.js +155 -0
- package/dist/src/core/model-policy/store.js +189 -0
- package/dist/src/core/model-policy/types.js +2 -0
- package/dist/src/core/model-policy/validate.js +112 -0
- package/dist/src/core/plan/identity.js +4 -2
- package/dist/src/core/plan/validate.js +15 -16
- package/dist/src/index.js +2 -0
- package/dist/tests/commands/job/gate-reconcile.test.js +16 -0
- package/dist/tests/commands/job/gate.test.js +9 -0
- package/dist/tests/commands/job/routing-report-cli.test.js +46 -0
- package/dist/tests/commands/job/routing.test.js +11 -0
- package/dist/tests/commands/model-policy/index.test.js +117 -0
- package/dist/tests/commands/plan/index.test.js +52 -0
- package/dist/tests/commands/watch/apply.test.js +12 -0
- package/dist/tests/core/admission/index.test.js +53 -0
- package/dist/tests/core/model-policy/capabilities.test.js +365 -0
- package/dist/tests/core/model-policy/digest.test.js +117 -0
- package/dist/tests/core/model-policy/journal.test.js +46 -0
- package/dist/tests/core/model-policy/policy.test.js +47 -0
- package/dist/tests/core/model-policy/resolve.test.js +185 -0
- package/dist/tests/core/model-policy/store.test.js +177 -0
- package/dist/tests/core/plan/validate.test.js +40 -2
- package/dist/tests/core/secure-fs/native-bridge.test.js +81 -4
- package/dist/tests/structural/directory-fsync-is-single-source.test.js +97 -0
- package/dist/tests/structural/native-release-artifacts.test.js +14 -0
- package/package.json +3 -2
- package/prebuilds/darwin-arm64/secure_fs.node +0 -0
- package/prebuilds/darwin-x64/secure_fs.node +0 -0
- package/prebuilds/linux-arm64/secure_fs.node +0 -0
- package/prebuilds/linux-x64/secure_fs.node +0 -0
- package/prebuilds/win32-arm64/secure_fs.node +0 -0
- package/prebuilds/win32-x64/secure_fs.node +0 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const path_1 = __importDefault(require("path"));
|
|
7
|
+
const resolve_1 = require("../../../src/core/model-policy/resolve");
|
|
8
|
+
const resolve_2 = require("../../../src/core/model-policy/resolve");
|
|
9
|
+
const validate_1 = require("../../../src/core/plan/validate");
|
|
10
|
+
const sha = 'a'.repeat(64);
|
|
11
|
+
const selection = (id, value) => ({ selector: { kind: 'model', id }, effort: { kind: 'explicit', value } });
|
|
12
|
+
const runtimeDefaultSelection = (id) => ({ selector: { kind: 'model', id }, effort: { kind: 'runtime-default' } });
|
|
13
|
+
const verifiedV1Plan = (0, validate_1.validatePlanFile)('docs/plans/2026-09-14-compact-only-bootstrap-plan.md', path_1.default.resolve(__dirname, '../../../..'));
|
|
14
|
+
if (verifiedV1Plan.state !== 'valid')
|
|
15
|
+
throw new Error(`tracked bootstrap plan must validate before resolver tests: ${verifiedV1Plan.state}`);
|
|
16
|
+
const policy = () => ({ schema: 'approved-model-policy/v1', content: { schema: 'model-policy/v1', mappings: [{ target: 'codex', runtimeKind: 'native', profiles: { mechanical: selection('mechanical', 'low'), integration: selection('integration', 'medium'), judgment: selection('judgment', 'medium') }, fullCapability: selection('full', 'high'), degradation: { allowMissingModelOverride: false, allowMissingEffortOverride: false, allowMissingObservedIdentity: false } }], implementationBudget: { maxAttempts: 3, escalation: ['mechanical', 'integration', 'judgment'], judgmentEfforts: ['medium', 'high'] } }, contentDigest: sha, approval: { approvedAt: '2026-09-17T00:00:00.000Z', approvalId: 'approval-1' }, lineage: { previousDigest: null } });
|
|
17
|
+
const capabilities = () => ({ schema: 'routing-capabilities/v1', runtime: { target: 'codex', kind: 'native', version: '1.0.0', accountScopeDigest: sha }, recordedAt: '2026-09-17T00:00:00.000Z', expiresAt: '2026-09-18T00:00:00.000Z', capabilities: { artifactDelivery: 'unverified', interactiveExecution: 'supported', unattendedController: 'supported', nativeSubagents: 'unverified', modelOverride: 'supported', effortOverride: 'supported', observedModelEvidence: 'supported', durableResume: 'unverified' }, availableSelections: [selection('mechanical', 'low'), selection('integration', 'medium'), selection('judgment', 'medium'), selection('full', 'high')], evidence: ['interactiveExecution', 'unattendedController', 'modelOverride', 'effortOverride', 'observedModelEvidence'].map(capability => ({ capability, kind: 'native-control', receiptDigest: sha })), approval: { approvalId: 'approval-1', snapshotDigest: sha } });
|
|
18
|
+
const input = () => ({ role: 'implementer', requestedProfile: 'mechanical', policy: policy(), capabilities: capabilities(), runtime: capabilities().runtime, now: new Date('2026-09-17T12:00:00.000Z') });
|
|
19
|
+
describe('resolveSelection', () => {
|
|
20
|
+
it('rejects a structurally fabricated v1 report before resolution', () => {
|
|
21
|
+
expect(() => (0, resolve_2.resolveV1Dispatch)({ ...input(), plan: { state: 'valid', schema: 'compact-slices/v1', planDigest: sha, manifest: {} }, optInV1: false })).toThrow(/invalid valid report/);
|
|
22
|
+
});
|
|
23
|
+
it('fails loudly for an unknown runtime role or requested profile', () => {
|
|
24
|
+
expect(() => (0, resolve_1.resolveSelection)({ ...input(), role: 'renderer' })).toThrow(/role is invalid/);
|
|
25
|
+
expect(() => (0, resolve_1.resolveSelection)({ ...input(), requestedProfile: 'cheap' })).toThrow(/requestedProfile is invalid/);
|
|
26
|
+
});
|
|
27
|
+
it.each(['antigravity', 'opencode', 'claude-code', 'cursor', 'copilot'])('does not infer routed support for %s from any renderer fact', target => {
|
|
28
|
+
const value = input();
|
|
29
|
+
value.runtime = { ...value.runtime, target };
|
|
30
|
+
value.capabilities.runtime = { ...value.capabilities.runtime, target };
|
|
31
|
+
expect((0, resolve_1.resolveSelection)(value)).toMatchObject({ state: 'blocked' });
|
|
32
|
+
});
|
|
33
|
+
it('accepts codex only through its explicit native receipt fixture', () => expect((0, resolve_1.resolveSelection)(input())).toMatchObject({ state: 'resolved' }));
|
|
34
|
+
it.each(['antigravity', 'opencode', 'claude-code', 'codex', 'cursor', 'copilot'])('resolves an explicit matching policy and native receipt for %s', target => {
|
|
35
|
+
const value = input();
|
|
36
|
+
value.policy.content.mappings = ['antigravity', 'opencode', 'claude-code', 'codex', 'cursor', 'copilot'].map(rowTarget => ({ ...structuredClone(value.policy.content.mappings[0]), target: rowTarget }));
|
|
37
|
+
value.runtime = { ...value.runtime, target };
|
|
38
|
+
value.capabilities.runtime = { ...value.capabilities.runtime, target };
|
|
39
|
+
expect((0, resolve_1.resolveSelection)(value)).toMatchObject({ state: 'resolved' });
|
|
40
|
+
});
|
|
41
|
+
it('blocks an explicit unverified override despite a complete target mapping', () => {
|
|
42
|
+
const value = input();
|
|
43
|
+
value.capabilities.capabilities.modelOverride = 'unverified';
|
|
44
|
+
expect((0, resolve_1.resolveSelection)(value)).toMatchObject({ state: 'blocked', diagnostics: [{ code: 'ROUTING_MODEL_OVERRIDE_UNAVAILABLE' }] });
|
|
45
|
+
});
|
|
46
|
+
it.each(['specification-reviewer', 'code-quality-reviewer', 'final-reviewer', 'architecture', 'track-a-qa', 'track-b-qa', 'controller', 'documentation', 'retro', 'finishing'])('routes %s to full capability independently of requested profile', role => {
|
|
47
|
+
const result = (0, resolve_1.resolveSelection)({ ...input(), role, requestedProfile: 'mechanical' });
|
|
48
|
+
expect(result.state).toBe('resolved');
|
|
49
|
+
if (result.state === 'resolved')
|
|
50
|
+
expect(result.selection).toEqual(selection('full', 'high'));
|
|
51
|
+
});
|
|
52
|
+
it('blocks a receipt whose runtime account differs even when selections match', () => {
|
|
53
|
+
const value = input();
|
|
54
|
+
value.runtime = { ...value.runtime, accountScopeDigest: 'b'.repeat(64) };
|
|
55
|
+
expect((0, resolve_1.resolveSelection)(value)).toMatchObject({ state: 'blocked', diagnostics: [{ code: 'ROUTING_CAPABILITY_RUNTIME_MISMATCH' }] });
|
|
56
|
+
});
|
|
57
|
+
it('blocks an unavailable approved selection instead of silently substituting', () => {
|
|
58
|
+
const value = input();
|
|
59
|
+
value.capabilities.availableSelections = [selection('full', 'high')];
|
|
60
|
+
expect((0, resolve_1.resolveSelection)(value)).toMatchObject({ state: 'blocked', diagnostics: [{ code: 'ROUTING_SELECTION_UNAVAILABLE' }] });
|
|
61
|
+
});
|
|
62
|
+
it('blocks a corrupt receipt before trusting its availability list', () => {
|
|
63
|
+
const value = input();
|
|
64
|
+
value.capabilities.availableSelections = [];
|
|
65
|
+
expect((0, resolve_1.resolveSelection)(value)).toMatchObject({ state: 'blocked', diagnostics: [{ code: 'ROUTING_CAPABILITY_INVALID' }] });
|
|
66
|
+
});
|
|
67
|
+
it('degrades only to an attested matching full default when a missing override is approved', () => {
|
|
68
|
+
const value = input();
|
|
69
|
+
value.policy.content.mappings[0].degradation.allowMissingModelOverride = true;
|
|
70
|
+
value.capabilities.capabilities.modelOverride = 'unsupported';
|
|
71
|
+
value.capabilities.runtimeDefaultSelection = selection('full', 'high');
|
|
72
|
+
expect((0, resolve_1.resolveSelection)(value)).toMatchObject({ state: 'resolved', outcome: 'degraded', selection: selection('full', 'high') });
|
|
73
|
+
});
|
|
74
|
+
it('blocks missing override where its full default is not actually attested', () => {
|
|
75
|
+
const value = input();
|
|
76
|
+
value.policy.content.mappings[0].degradation.allowMissingModelOverride = true;
|
|
77
|
+
value.capabilities.capabilities.modelOverride = 'unsupported';
|
|
78
|
+
expect((0, resolve_1.resolveSelection)(value)).toMatchObject({ state: 'blocked', diagnostics: [{ code: 'ROUTING_MODEL_OVERRIDE_UNAVAILABLE' }] });
|
|
79
|
+
});
|
|
80
|
+
it('requires effort override for an explicit native full selection', () => {
|
|
81
|
+
const value = input();
|
|
82
|
+
value.capabilities.capabilities.effortOverride = 'unsupported';
|
|
83
|
+
expect((0, resolve_1.resolveSelection)({ ...value, role: 'code-quality-reviewer' })).toMatchObject({ state: 'blocked', diagnostics: [{ code: 'ROUTING_EFFORT_OVERRIDE_UNAVAILABLE' }] });
|
|
84
|
+
});
|
|
85
|
+
it('does not require effort override for an attested runtime-default native full selection', () => {
|
|
86
|
+
const value = input();
|
|
87
|
+
value.policy.content.mappings[0].fullCapability = runtimeDefaultSelection('full');
|
|
88
|
+
value.capabilities.availableSelections = [selection('mechanical', 'low'), selection('integration', 'medium'), selection('judgment', 'medium'), runtimeDefaultSelection('full')];
|
|
89
|
+
value.capabilities.runtimeDefaultSelection = runtimeDefaultSelection('full');
|
|
90
|
+
value.capabilities.capabilities.effortOverride = 'unsupported';
|
|
91
|
+
expect((0, resolve_1.resolveSelection)({ ...value, role: 'code-quality-reviewer' })).toMatchObject({ state: 'resolved', outcome: 'native', selection: runtimeDefaultSelection('full') });
|
|
92
|
+
});
|
|
93
|
+
it('does not require effort override for an attested runtime-default v1 full selection', () => {
|
|
94
|
+
const value = input();
|
|
95
|
+
value.policy.content.mappings[0].fullCapability = runtimeDefaultSelection('full');
|
|
96
|
+
value.capabilities.availableSelections = [selection('mechanical', 'low'), selection('integration', 'medium'), selection('judgment', 'medium'), runtimeDefaultSelection('full')];
|
|
97
|
+
value.capabilities.runtimeDefaultSelection = runtimeDefaultSelection('full');
|
|
98
|
+
value.capabilities.capabilities.effortOverride = 'unsupported';
|
|
99
|
+
expect((0, resolve_2.resolveV1Dispatch)({ ...value, plan: verifiedV1Plan, optInV1: true })).toMatchObject({ state: 'resolved', outcome: 'native', selection: runtimeDefaultSelection('full') });
|
|
100
|
+
});
|
|
101
|
+
// Regression for the collapse that made routed model and surrendered effort
|
|
102
|
+
// indistinguishable: degrading effort used to rewrite the selection to
|
|
103
|
+
// fullCapability, so a mechanical slice silently ran the judgment model while
|
|
104
|
+
// the only evidence emitted named effortOverride.
|
|
105
|
+
it('keeps the routed per-profile model when effort override degrades', () => {
|
|
106
|
+
const value = input();
|
|
107
|
+
value.policy.content.mappings[0].degradation.allowMissingEffortOverride = true;
|
|
108
|
+
value.capabilities.capabilities.effortOverride = 'unsupported';
|
|
109
|
+
value.capabilities.runtimeDefaultSelection = runtimeDefaultSelection('full');
|
|
110
|
+
value.capabilities.availableSelections = [...value.capabilities.availableSelections, runtimeDefaultSelection('mechanical'), runtimeDefaultSelection('integration'), runtimeDefaultSelection('full')];
|
|
111
|
+
expect((0, resolve_1.resolveSelection)({ ...value, requestedProfile: 'mechanical' })).toMatchObject({ state: 'resolved', outcome: 'degraded', selection: runtimeDefaultSelection('mechanical'), effectiveProfile: 'mechanical', unavailableEvidence: ['effortOverride'] });
|
|
112
|
+
expect((0, resolve_1.resolveSelection)({ ...value, requestedProfile: 'integration' })).toMatchObject({ state: 'resolved', outcome: 'degraded', selection: runtimeDefaultSelection('integration'), effectiveProfile: 'integration', unavailableEvidence: ['effortOverride'] });
|
|
113
|
+
});
|
|
114
|
+
// An honest receipt for a runtime with no effort control attests ONLY
|
|
115
|
+
// runtime-default selections — it cannot claim `mechanical at low effort` is
|
|
116
|
+
// selectable. Checking availability before degradation rejected exactly that
|
|
117
|
+
// receipt, so the contract only worked for a receipt that overclaimed.
|
|
118
|
+
it('degrades effort against a receipt that attests only runtime-default selections', () => {
|
|
119
|
+
const value = input();
|
|
120
|
+
value.policy.content.mappings[0].degradation.allowMissingEffortOverride = true;
|
|
121
|
+
value.capabilities.capabilities.effortOverride = 'unsupported';
|
|
122
|
+
value.capabilities.availableSelections = [runtimeDefaultSelection('mechanical'), runtimeDefaultSelection('integration'), runtimeDefaultSelection('judgment'), runtimeDefaultSelection('full')];
|
|
123
|
+
value.capabilities.runtimeDefaultSelection = runtimeDefaultSelection('full');
|
|
124
|
+
expect((0, resolve_1.resolveSelection)({ ...value, requestedProfile: 'mechanical' })).toMatchObject({ state: 'resolved', outcome: 'degraded', selection: runtimeDefaultSelection('mechanical'), unavailableEvidence: ['effortOverride'] });
|
|
125
|
+
});
|
|
126
|
+
// The runtime default is one selection; a per-profile selection at
|
|
127
|
+
// runtime-default effort is a different one. Requiring them equal made every
|
|
128
|
+
// profile but the default model unroutable, with modelOverride attested
|
|
129
|
+
// supported and the selection itself attested available.
|
|
130
|
+
it('routes a per-profile runtime-default selection whose model differs from the runtime default', () => {
|
|
131
|
+
const value = input();
|
|
132
|
+
value.policy.content.mappings[0].profiles.mechanical = runtimeDefaultSelection('mechanical');
|
|
133
|
+
value.capabilities.availableSelections = [runtimeDefaultSelection('mechanical'), runtimeDefaultSelection('full')];
|
|
134
|
+
value.capabilities.runtimeDefaultSelection = runtimeDefaultSelection('full');
|
|
135
|
+
expect((0, resolve_1.resolveSelection)({ ...value, requestedProfile: 'mechanical' })).toMatchObject({ state: 'resolved', outcome: 'native', selection: runtimeDefaultSelection('mechanical'), effectiveProfile: 'mechanical' });
|
|
136
|
+
});
|
|
137
|
+
it('still blocks a runtime-default selection the receipt does not attest at all', () => {
|
|
138
|
+
const value = input();
|
|
139
|
+
value.policy.content.mappings[0].profiles.mechanical = runtimeDefaultSelection('mechanical');
|
|
140
|
+
value.capabilities.availableSelections = [runtimeDefaultSelection('full')];
|
|
141
|
+
value.capabilities.runtimeDefaultSelection = runtimeDefaultSelection('full');
|
|
142
|
+
expect((0, resolve_1.resolveSelection)({ ...value, requestedProfile: 'mechanical' })).toMatchObject({ state: 'blocked', diagnostics: [{ code: 'ROUTING_SELECTION_UNAVAILABLE' }] });
|
|
143
|
+
});
|
|
144
|
+
it('still blocks a runtime-default selection when the receipt declares no runtime default', () => {
|
|
145
|
+
const value = input();
|
|
146
|
+
value.policy.content.mappings[0].profiles.mechanical = runtimeDefaultSelection('mechanical');
|
|
147
|
+
value.capabilities.availableSelections = [runtimeDefaultSelection('mechanical')];
|
|
148
|
+
delete value.capabilities.runtimeDefaultSelection;
|
|
149
|
+
expect((0, resolve_1.resolveSelection)({ ...value, requestedProfile: 'mechanical' })).toMatchObject({ state: 'blocked', diagnostics: [{ code: 'ROUTING_SELECTION_UNAVAILABLE' }] });
|
|
150
|
+
});
|
|
151
|
+
it('blocks effort degradation when the routed model at runtime-default effort is not attested', () => {
|
|
152
|
+
const value = input();
|
|
153
|
+
value.policy.content.mappings[0].degradation.allowMissingEffortOverride = true;
|
|
154
|
+
value.capabilities.capabilities.effortOverride = 'unsupported';
|
|
155
|
+
value.capabilities.runtimeDefaultSelection = runtimeDefaultSelection('full');
|
|
156
|
+
value.capabilities.availableSelections = [...value.capabilities.availableSelections, runtimeDefaultSelection('full')];
|
|
157
|
+
expect((0, resolve_1.resolveSelection)({ ...value, requestedProfile: 'mechanical' })).toMatchObject({ state: 'blocked', diagnostics: [{ code: 'ROUTING_EFFORT_OVERRIDE_UNAVAILABLE' }] });
|
|
158
|
+
});
|
|
159
|
+
it('keeps effort strict when the mapping does not allow degrading it', () => {
|
|
160
|
+
const value = input();
|
|
161
|
+
value.capabilities.capabilities.effortOverride = 'unsupported';
|
|
162
|
+
value.capabilities.runtimeDefaultSelection = runtimeDefaultSelection('full');
|
|
163
|
+
value.capabilities.availableSelections = [...value.capabilities.availableSelections, runtimeDefaultSelection('mechanical'), runtimeDefaultSelection('full')];
|
|
164
|
+
expect(value.policy.content.mappings[0].degradation.allowMissingEffortOverride).toBe(false);
|
|
165
|
+
expect((0, resolve_1.resolveSelection)({ ...value, requestedProfile: 'mechanical' })).toMatchObject({ state: 'blocked', diagnostics: [{ code: 'ROUTING_EFFORT_OVERRIDE_UNAVAILABLE' }] });
|
|
166
|
+
});
|
|
167
|
+
it('never degrades effort on an unverified capability, only on an attested unsupported one', () => {
|
|
168
|
+
const value = input();
|
|
169
|
+
value.policy.content.mappings[0].degradation.allowMissingEffortOverride = true;
|
|
170
|
+
value.capabilities.capabilities.effortOverride = 'unverified';
|
|
171
|
+
value.capabilities.runtimeDefaultSelection = runtimeDefaultSelection('full');
|
|
172
|
+
value.capabilities.availableSelections = [...value.capabilities.availableSelections, runtimeDefaultSelection('mechanical'), runtimeDefaultSelection('full')];
|
|
173
|
+
expect((0, resolve_1.resolveSelection)({ ...value, requestedProfile: 'mechanical' })).toMatchObject({ state: 'blocked', diagnostics: [{ code: 'ROUTING_EFFORT_OVERRIDE_UNAVAILABLE', message: expect.stringMatching(/unverified/) }] });
|
|
174
|
+
});
|
|
175
|
+
it('does not require effort override after model fallback selects an attested runtime-default full selection', () => {
|
|
176
|
+
const value = input();
|
|
177
|
+
value.policy.content.mappings[0].fullCapability = runtimeDefaultSelection('full');
|
|
178
|
+
value.capabilities.availableSelections = [selection('mechanical', 'low'), selection('integration', 'medium'), selection('judgment', 'medium'), runtimeDefaultSelection('full')];
|
|
179
|
+
value.capabilities.runtimeDefaultSelection = runtimeDefaultSelection('full');
|
|
180
|
+
value.policy.content.mappings[0].degradation.allowMissingModelOverride = true;
|
|
181
|
+
value.capabilities.capabilities.modelOverride = 'unsupported';
|
|
182
|
+
value.capabilities.capabilities.effortOverride = 'unsupported';
|
|
183
|
+
expect((0, resolve_1.resolveSelection)(value)).toMatchObject({ state: 'resolved', outcome: 'degraded', selection: runtimeDefaultSelection('full'), unavailableEvidence: ['modelOverride'] });
|
|
184
|
+
});
|
|
185
|
+
});
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const os_1 = __importDefault(require("os"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const atomic_file_1 = require("../../../src/core/atomic-file");
|
|
10
|
+
const canonical_1 = require("../../../src/core/model-policy/canonical");
|
|
11
|
+
const store_1 = require("../../../src/core/model-policy/store");
|
|
12
|
+
const paths_1 = require("../../../src/core/model-policy/paths");
|
|
13
|
+
const policy = () => ({ schema: 'model-policy/v1', mappings: [{ target: 'codex', runtimeKind: 'native', profiles: {
|
|
14
|
+
mechanical: { selector: { kind: 'model', id: 'gpt-5.6-luna' }, effort: { kind: 'explicit', value: 'medium' } },
|
|
15
|
+
integration: { selector: { kind: 'model', id: 'gpt-5.6-terra' }, effort: { kind: 'explicit', value: 'medium' } },
|
|
16
|
+
judgment: { selector: { kind: 'model', id: 'gpt-5.6-sol' }, effort: { kind: 'explicit', value: 'medium' } },
|
|
17
|
+
}, fullCapability: { selector: { kind: 'model', id: 'gpt-5.6-sol' }, effort: { kind: 'explicit', value: 'high' } }, degradation: { allowMissingModelOverride: false, allowMissingEffortOverride: false, allowMissingObservedIdentity: false } }], implementationBudget: { maxAttempts: 3, escalation: ['mechanical', 'integration', 'judgment'], judgmentEfforts: ['medium', 'high'] } });
|
|
18
|
+
function createIdentityFencedBoundary() {
|
|
19
|
+
const observations = new WeakMap();
|
|
20
|
+
return {
|
|
21
|
+
withProjectLease: (_root, operation) => operation(),
|
|
22
|
+
readRegularFile: (file) => {
|
|
23
|
+
const identity = Object.freeze({});
|
|
24
|
+
const bytes = fs_1.default.readFileSync(file);
|
|
25
|
+
observations.set(identity, { file, bytes });
|
|
26
|
+
return { bytes, identity };
|
|
27
|
+
},
|
|
28
|
+
writeProjectTransaction: (root, destination, content, options) => {
|
|
29
|
+
const file = path_1.default.join(root, destination);
|
|
30
|
+
fs_1.default.mkdirSync(path_1.default.dirname(file), { recursive: true });
|
|
31
|
+
if (options.mode === 'create' && fs_1.default.existsSync(file))
|
|
32
|
+
throw new Error('project destination already exists');
|
|
33
|
+
if (options.mode === 'replace') {
|
|
34
|
+
const observed = observations.get(options.expectedIdentity);
|
|
35
|
+
if (!observed || observed.file !== file || !observed.bytes.equals(options.expected) || !fs_1.default.readFileSync(file).equals(options.expected))
|
|
36
|
+
throw new Error('original changed before fenced replacement');
|
|
37
|
+
}
|
|
38
|
+
const temporary = `${file}.test-tmp`;
|
|
39
|
+
const fd = fs_1.default.openSync(temporary, 'wx', 0o600);
|
|
40
|
+
try {
|
|
41
|
+
fs_1.default.writeFileSync(fd, content);
|
|
42
|
+
fs_1.default.fsyncSync(fd);
|
|
43
|
+
fs_1.default.closeSync(fd);
|
|
44
|
+
fs_1.default.renameSync(temporary, file);
|
|
45
|
+
// La durabilidad de la ENTRADA publicada se pide por el unico helper
|
|
46
|
+
// que define esa politica (atomic-file.fsyncDirSync): fail-closed en
|
|
47
|
+
// POSIX y degradado SOLO ante el gap conocido de win32 (EPERM al
|
|
48
|
+
// fsync-ear un fd de directorio, capacidad que el SO no expone).
|
|
49
|
+
// Reimplementarla aca la hacia divergir y rompia la matriz Windows.
|
|
50
|
+
(0, atomic_file_1.fsyncDirSync)(path_1.default.dirname(file));
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
try {
|
|
54
|
+
fs_1.default.rmSync(temporary, { force: true });
|
|
55
|
+
}
|
|
56
|
+
catch { /* preserve transaction result */ }
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const identityFencedBoundary = createIdentityFencedBoundary();
|
|
62
|
+
const approve = (input) => (0, store_1.approvePolicyWithBoundary)(input, identityFencedBoundary);
|
|
63
|
+
describe('approved policy store', () => {
|
|
64
|
+
let root;
|
|
65
|
+
let oldHome;
|
|
66
|
+
let oldAwmHome;
|
|
67
|
+
beforeEach(() => { root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-policy-')); oldHome = process.env.HOME; oldAwmHome = process.env.AWM_HOME; process.env.AWM_HOME = path_1.default.join(root, 'operator'); });
|
|
68
|
+
afterEach(() => { jest.restoreAllMocks(); delete process.env.POLICY_TEST_FAIL_WRITE; delete process.env.POLICY_TEST_MUTATE_DURING_REPLACE; if (oldHome === undefined)
|
|
69
|
+
delete process.env.HOME;
|
|
70
|
+
else
|
|
71
|
+
process.env.HOME = oldHome; if (oldAwmHome === undefined)
|
|
72
|
+
delete process.env.AWM_HOME;
|
|
73
|
+
else
|
|
74
|
+
process.env.AWM_HOME = oldAwmHome; fs_1.default.rmSync(root, { recursive: true, force: true }); });
|
|
75
|
+
const candidate = () => { const file = path_1.default.join(root, 'candidate.json'); fs_1.default.writeFileSync(file, JSON.stringify(policy())); return file; };
|
|
76
|
+
it('publishes only an expected digest and reads it without creating state', () => {
|
|
77
|
+
const file = candidate();
|
|
78
|
+
const digest = (0, canonical_1.canonicalPolicyDigest)(policy());
|
|
79
|
+
expect((0, store_1.readEffectivePolicy)(root)).toEqual({ state: 'absent' });
|
|
80
|
+
const approved = approve({ file, scope: 'user', cwd: root, expectedDigest: digest });
|
|
81
|
+
expect(approved.contentDigest).toBe(digest);
|
|
82
|
+
expect((0, store_1.readEffectivePolicy)(root)).toMatchObject({ state: 'approved', provenance: 'user', policy: { contentDigest: digest } });
|
|
83
|
+
});
|
|
84
|
+
it('rejects a mismatched digest before writing', () => {
|
|
85
|
+
const file = candidate();
|
|
86
|
+
expect(() => approve({ file, scope: 'user', cwd: root, expectedDigest: '0'.repeat(64) })).toThrow(/digest/i);
|
|
87
|
+
expect(fs_1.default.existsSync((0, paths_1.userPolicyPath)())).toBe(false);
|
|
88
|
+
});
|
|
89
|
+
it('allows exactly one initial writer and exact CAS replacement', () => {
|
|
90
|
+
const file = candidate();
|
|
91
|
+
const digest = (0, canonical_1.canonicalPolicyDigest)(policy());
|
|
92
|
+
approve({ file, scope: 'user', cwd: root, expectedDigest: digest });
|
|
93
|
+
expect(() => approve({ file, scope: 'user', cwd: root, expectedDigest: digest })).toThrow(/replace|existing/i);
|
|
94
|
+
expect(() => approve({ file, scope: 'user', cwd: root, expectedDigest: digest, replaceDigest: 'f'.repeat(64) })).toThrow(/predecessor|replace/i);
|
|
95
|
+
expect(approve({ file, scope: 'user', cwd: root, expectedDigest: digest, replaceDigest: digest }).lineage.previousDigest).toBe(digest);
|
|
96
|
+
});
|
|
97
|
+
it('rejects an unexpected opaque identity token before replacing bytes', () => {
|
|
98
|
+
const target = path_1.default.join(root, 'identity.json');
|
|
99
|
+
fs_1.default.writeFileSync(target, 'observed');
|
|
100
|
+
const observed = identityFencedBoundary.readRegularFile(target, 1024);
|
|
101
|
+
expect(() => identityFencedBoundary.writeProjectTransaction(root, 'identity.json', Buffer.from('replacement'), {
|
|
102
|
+
mode: 'replace', createParents: false, expected: observed.bytes, expectedIdentity: Object.freeze({}),
|
|
103
|
+
})).toThrow(/original changed/i);
|
|
104
|
+
expect(fs_1.default.readFileSync(target, 'utf8')).toBe('observed');
|
|
105
|
+
});
|
|
106
|
+
it('resolves a relative candidate through cwd instead of process cwd', () => {
|
|
107
|
+
const cwd = path_1.default.join(root, 'project');
|
|
108
|
+
fs_1.default.mkdirSync(cwd);
|
|
109
|
+
const file = path_1.default.join(cwd, 'candidate.json');
|
|
110
|
+
fs_1.default.writeFileSync(file, JSON.stringify(policy()));
|
|
111
|
+
const digest = (0, canonical_1.canonicalPolicyDigest)(policy());
|
|
112
|
+
expect(approve({ file: 'candidate.json', scope: 'project', cwd, expectedDigest: digest }).contentDigest).toBe(digest);
|
|
113
|
+
});
|
|
114
|
+
it('accepts exactly one of two competing fenced replacements', () => {
|
|
115
|
+
const file = candidate();
|
|
116
|
+
const digest = (0, canonical_1.canonicalPolicyDigest)(policy());
|
|
117
|
+
approve({ file, scope: 'user', cwd: root, expectedDigest: digest });
|
|
118
|
+
const input = { file, scope: 'user', cwd: root, expectedDigest: digest, replaceDigest: digest };
|
|
119
|
+
let entered = false;
|
|
120
|
+
let accepted = 0;
|
|
121
|
+
const boundary = {
|
|
122
|
+
withProjectLease: (_root, operation) => operation(),
|
|
123
|
+
readRegularFile: (target) => ({ bytes: fs_1.default.readFileSync(target), identity: Object.freeze({}) }),
|
|
124
|
+
writeProjectTransaction: (base, destination, bytes, options) => {
|
|
125
|
+
const target = path_1.default.join(base, destination);
|
|
126
|
+
if (options.mode === 'replace' && !entered) {
|
|
127
|
+
entered = true;
|
|
128
|
+
// Model a non-cooperating external process: the native
|
|
129
|
+
// identity fence, not this advisory lock, must reject us.
|
|
130
|
+
fs_1.default.rmSync(`${target}.lock`);
|
|
131
|
+
(0, store_1.approvePolicyWithBoundary)(input, boundary);
|
|
132
|
+
fs_1.default.writeFileSync(`${target}.lock`, 'outer lock placeholder');
|
|
133
|
+
}
|
|
134
|
+
if (options.mode === 'replace' && (!options.expected || !fs_1.default.readFileSync(target).equals(options.expected)))
|
|
135
|
+
throw new Error('original changed before fenced replacement');
|
|
136
|
+
fs_1.default.mkdirSync(path_1.default.dirname(target), { recursive: true });
|
|
137
|
+
fs_1.default.writeFileSync(target, bytes);
|
|
138
|
+
accepted++;
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
expect(() => (0, store_1.approvePolicyWithBoundary)(input, boundary)).toThrow(/changed|replacement/i);
|
|
142
|
+
expect(accepted).toBe(1);
|
|
143
|
+
});
|
|
144
|
+
it('returns a failed publication and preserves predecessor bytes on injected transaction failure', () => {
|
|
145
|
+
const file = candidate();
|
|
146
|
+
const digest = (0, canonical_1.canonicalPolicyDigest)(policy());
|
|
147
|
+
approve({ file, scope: 'user', cwd: root, expectedDigest: digest });
|
|
148
|
+
const before = fs_1.default.readFileSync((0, paths_1.userPolicyPath)());
|
|
149
|
+
const boundary = {
|
|
150
|
+
withProjectLease: (_root, operation) => operation(),
|
|
151
|
+
readRegularFile: (target) => ({ bytes: fs_1.default.readFileSync(target), identity: Object.freeze({}) }),
|
|
152
|
+
writeProjectTransaction: () => { throw new Error('injected durable publication failure'); },
|
|
153
|
+
};
|
|
154
|
+
expect(() => (0, store_1.approvePolicyWithBoundary)({ file, scope: 'user', cwd: root, expectedDigest: digest, replaceDigest: digest }, boundary)).toThrow(/publication failure/);
|
|
155
|
+
expect(fs_1.default.readFileSync((0, paths_1.userPolicyPath)()).equals(before)).toBe(true);
|
|
156
|
+
});
|
|
157
|
+
it('blocks an invalid project policy instead of falling back to a valid user policy', () => {
|
|
158
|
+
const file = candidate();
|
|
159
|
+
const digest = (0, canonical_1.canonicalPolicyDigest)(policy());
|
|
160
|
+
approve({ file, scope: 'user', cwd: root, expectedDigest: digest });
|
|
161
|
+
fs_1.default.mkdirSync(path_1.default.join(root, '.awm'));
|
|
162
|
+
fs_1.default.writeFileSync(path_1.default.join(root, '.awm', 'model-policy.json'), '{"content":');
|
|
163
|
+
expect((0, store_1.readEffectivePolicy)(root)).toMatchObject({ state: 'invalid', provenance: 'project' });
|
|
164
|
+
});
|
|
165
|
+
it('rejects duplicate JSON keys and symlinked parent or destination without touching the target', () => {
|
|
166
|
+
const duplicate = path_1.default.join(root, 'duplicate.json');
|
|
167
|
+
fs_1.default.writeFileSync(duplicate, '{"schema":"model-policy/v1","schema":"model-policy/v1"}');
|
|
168
|
+
expect(() => approve({ file: duplicate, scope: 'user', cwd: root, expectedDigest: 'a'.repeat(64) })).toThrow(/duplicate/i);
|
|
169
|
+
const file = candidate();
|
|
170
|
+
const digest = (0, canonical_1.canonicalPolicyDigest)(policy());
|
|
171
|
+
fs_1.default.mkdirSync(process.env.AWM_HOME);
|
|
172
|
+
fs_1.default.symlinkSync(root, path_1.default.join(process.env.AWM_HOME, 'link'));
|
|
173
|
+
const target = (0, paths_1.userPolicyPath)();
|
|
174
|
+
fs_1.default.symlinkSync(path_1.default.join(root, 'outside'), target);
|
|
175
|
+
expect(() => approve({ file, scope: 'user', cwd: root, expectedDigest: digest })).toThrow(/symlink/i);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
@@ -50,6 +50,15 @@ function fixture(root, mutate) {
|
|
|
50
50
|
fs_1.default.writeFileSync(plan, markdown);
|
|
51
51
|
return plan;
|
|
52
52
|
}
|
|
53
|
+
function v2Fixture(root, profile = 'mechanical') {
|
|
54
|
+
const plan = fixture(root, manifest => {
|
|
55
|
+
manifest.schema = 'compact-slices/v2';
|
|
56
|
+
const slices = manifest.slices;
|
|
57
|
+
slices[0].implementerProfile = profile;
|
|
58
|
+
});
|
|
59
|
+
fs_1.default.writeFileSync(plan, fs_1.default.readFileSync(plan, 'utf8').replace(START, '<!-- AWM:COMPACT-SLICES:START v2 -->').replace(END, '<!-- AWM:COMPACT-SLICES:END v2 -->'));
|
|
60
|
+
return plan;
|
|
61
|
+
}
|
|
53
62
|
describe('validatePlanFile', () => {
|
|
54
63
|
it('validates an authenticated snapshot without reopening a swapped plan parent', () => {
|
|
55
64
|
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-plan-snapshot-'));
|
|
@@ -86,6 +95,35 @@ describe('validatePlanFile', () => {
|
|
|
86
95
|
expect((0, validate_1.validatePlanFile)(plan, root)).toMatchObject({ state: 'valid', schema: 'compact-slices/v1' });
|
|
87
96
|
expect(fs_1.default.readFileSync(plan, 'utf8')).toBe(before);
|
|
88
97
|
});
|
|
98
|
+
test.each(['mechanical', 'integration', 'judgment'])('accepts v2 with the exact %s implementer profile', profile => {
|
|
99
|
+
const report = (0, validate_1.validatePlanFile)(v2Fixture(root, profile), root);
|
|
100
|
+
expect(report).toMatchObject({ state: 'valid', schema: 'compact-slices/v2', manifest: { slices: [expect.objectContaining({ implementerProfile: profile })] } });
|
|
101
|
+
});
|
|
102
|
+
test('rejects v1 profile without weakening exact keys', () => {
|
|
103
|
+
expect((0, validate_1.validatePlanFile)(fixture(root, manifest => { (manifest.slices[0]).implementerProfile = 'mechanical'; }), root)).toMatchObject({ state: 'invalid' });
|
|
104
|
+
});
|
|
105
|
+
test.each([
|
|
106
|
+
['missing profile', (text) => text.replace(/,\n\s*"implementerProfile": "mechanical"/, '')],
|
|
107
|
+
['concrete model', (text) => text.replace('"implementerProfile": "mechanical"', '"implementerProfile": "mechanical",\n "model": "vendor-model"')],
|
|
108
|
+
['unknown profile', (text) => text.replace('"implementerProfile": "mechanical"', '"implementerProfile": "cheap"')],
|
|
109
|
+
])('rejects v2 %s without weakening exact keys', (_name, mutate) => {
|
|
110
|
+
const plan = v2Fixture(root);
|
|
111
|
+
fs_1.default.writeFileSync(plan, mutate(fs_1.default.readFileSync(plan, 'utf8')));
|
|
112
|
+
expect((0, validate_1.validatePlanFile)(plan, root)).toMatchObject({ state: 'invalid' });
|
|
113
|
+
});
|
|
114
|
+
test('preserves v2 profile bytes in the execution identity', () => {
|
|
115
|
+
const mechanical = fs_1.default.readFileSync(v2Fixture(root, 'mechanical'), 'utf8');
|
|
116
|
+
const integration = mechanical.replace('"implementerProfile": "mechanical"', '"implementerProfile": "integration"');
|
|
117
|
+
const integrationPath = path_1.default.join(root, 'integration.md');
|
|
118
|
+
fs_1.default.writeFileSync(integrationPath, integration);
|
|
119
|
+
const left = (0, validate_1.validatePlanFile)(path_1.default.join(root, 'plan.md'), root);
|
|
120
|
+
const right = (0, validate_1.validatePlanFile)(integrationPath, root);
|
|
121
|
+
expect(left).toMatchObject({ state: 'valid', schema: 'compact-slices/v2' });
|
|
122
|
+
expect(right).toMatchObject({ state: 'valid', schema: 'compact-slices/v2' });
|
|
123
|
+
if (left.state !== 'valid' || right.state !== 'valid')
|
|
124
|
+
throw new Error('v2 fixture must validate');
|
|
125
|
+
expect(left.executionDigest).not.toBe(right.executionDigest);
|
|
126
|
+
});
|
|
89
127
|
test('makes an approved valid report and its nested manifest immutable after verification', () => {
|
|
90
128
|
const report = (0, validate_1.validatePlanFile)(fixture(root), root);
|
|
91
129
|
expectApprovedPlanValid(report);
|
|
@@ -436,7 +474,7 @@ describe('validatePlanFile', () => {
|
|
|
436
474
|
test('keeps a future schema behind partial markers invalid rather than unsupported', () => {
|
|
437
475
|
const plan = path_1.default.join(root, 'partial-future.md');
|
|
438
476
|
fs_1.default.writeFileSync(plan, `${START}\n{"schema":"compact-slices/v2"}`);
|
|
439
|
-
expect((0, validate_1.validatePlanFile)(plan, root)).toMatchObject({ state: '
|
|
477
|
+
expect((0, validate_1.validatePlanFile)(plan, root)).toMatchObject({ state: 'invalid', diagnostics: [expect.objectContaining({ code: 'PLAN_MARKERS' })] });
|
|
440
478
|
});
|
|
441
479
|
test('applies scalar limits before classifying a future schema as unsupported', () => {
|
|
442
480
|
const plan = path_1.default.join(root, 'future-too-large.md');
|
|
@@ -733,7 +771,7 @@ describe('validatePlanFile', () => {
|
|
|
733
771
|
test('recognizes a parseable future schema when only the END marker is broken', () => {
|
|
734
772
|
const plan = path_1.default.join(root, 'future-missing-end.md');
|
|
735
773
|
fs_1.default.writeFileSync(plan, `${START}\n{"schema":"compact-slices/v2"}\n`);
|
|
736
|
-
expect((0, validate_1.validatePlanFile)(plan, root)).toMatchObject({ state: '
|
|
774
|
+
expect((0, validate_1.validatePlanFile)(plan, root)).toMatchObject({ state: 'invalid', diagnostics: [expect.objectContaining({ code: 'PLAN_MARKERS' })] });
|
|
737
775
|
});
|
|
738
776
|
test('accepts CRLF Markdown with nested headings and fenced code in a required section', () => {
|
|
739
777
|
const plan = fixture(root);
|
|
@@ -160,6 +160,28 @@ describe('native secure-fs Windows source contract', () => {
|
|
|
160
160
|
expect(removal).not.toMatch(/\bDeleteFileW\b/);
|
|
161
161
|
});
|
|
162
162
|
});
|
|
163
|
+
describe('native secure-fs durable publication source contract', () => {
|
|
164
|
+
const source = () => fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../../../native/secure_fs.cc'), 'utf8').replace(/\r\n/g, '\n');
|
|
165
|
+
it('turns parent-directory fsync failure after publication into a bounded error', () => {
|
|
166
|
+
const native = source();
|
|
167
|
+
const write = native.slice(native.indexOf('napi_value WriteProjectTransaction'), native.indexOf('\n} // namespace'));
|
|
168
|
+
expect(native).toMatch(/if \(fsync\(parent\) != 0\)[\s\S]{0,240}Throw\(env, "secure-fs durable directory sync failed"\)/);
|
|
169
|
+
expect(write).toMatch(/fsync\(parent\) == 0;[\s\S]{0,180}secure-fs durable directory sync failed/);
|
|
170
|
+
const withoutWriteSync = write.replace('fsync(parent) == 0', 'true');
|
|
171
|
+
expect(withoutWriteSync).not.toMatch(/fsync\(parent\) == 0;/);
|
|
172
|
+
expect(withoutWriteSync).not.toMatch(/fsync\(parent\) == 0;[\s\S]{0,180}secure-fs durable directory sync failed/);
|
|
173
|
+
});
|
|
174
|
+
it('degrades only the unsupported Windows directory flush and retains all other durable failures', () => {
|
|
175
|
+
expect(source()).toMatch(/FlushFileBuffers\(parent\.handle\) == 0 && GetLastError\(\) != ERROR_ACCESS_DENIED/);
|
|
176
|
+
expect(source()).toMatch(/if \(directory_sync_failed\).*secure-fs durable directory sync failed/);
|
|
177
|
+
});
|
|
178
|
+
it('does not clean a staging name already consumed by a fenced POSIX rename', () => {
|
|
179
|
+
const write = source().slice(source().indexOf('napi_value WriteProjectTransaction'), source().indexOf('\n} // namespace'));
|
|
180
|
+
expect(write).toContain('const bool cleaned = options.replace || unlinkat(parent, temporary.c_str(), 0) == 0;');
|
|
181
|
+
const reverted = write.replace('options.replace || ', '');
|
|
182
|
+
expect(reverted).not.toContain('const bool cleaned = options.replace || unlinkat(parent, temporary.c_str(), 0) == 0;');
|
|
183
|
+
});
|
|
184
|
+
});
|
|
163
185
|
describe('native secure-fs POSIX source contract', () => {
|
|
164
186
|
const source = () => fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../../../native/secure_fs.cc'), 'utf8');
|
|
165
187
|
it('uses an unpredictable nonce and a bounded retry loop for descriptor-relative staging', () => {
|
|
@@ -399,6 +421,38 @@ const nativeFixtureAvailable = ['linux', 'darwin', 'win32'].includes(process.pla
|
|
|
399
421
|
&& ['x64', 'arm64'].includes(process.arch)
|
|
400
422
|
&& fs_1.default.existsSync(path_1.default.join(__dirname, '../../../prebuilds', `${process.platform}-${process.arch}`, 'secure_fs.node'));
|
|
401
423
|
const nativeOnly = nativeFixtureAvailable ? describe : describe.skip;
|
|
424
|
+
const testAddonPath = path_1.default.join(__dirname, '../../../native/build/Release/secure_fs_test.node');
|
|
425
|
+
const nativeTestOnly = describe;
|
|
426
|
+
nativeTestOnly('native secure-fs directory-sync fault injection', () => {
|
|
427
|
+
let root;
|
|
428
|
+
let testBinding;
|
|
429
|
+
beforeEach(() => {
|
|
430
|
+
expect(fs_1.default.existsSync(testAddonPath)).toBe(true);
|
|
431
|
+
root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-secure-fs-fsync-'));
|
|
432
|
+
testBinding = require(testAddonPath);
|
|
433
|
+
});
|
|
434
|
+
afterEach(() => { testBinding.setDirectoryFsyncFailureForTests(false); fs_1.default.rmSync(root, { recursive: true, force: true }); });
|
|
435
|
+
it('writes the target then reports the forced post-publication directory-sync failure', () => {
|
|
436
|
+
testBinding.setDirectoryFsyncFailureForTests(true);
|
|
437
|
+
expect(() => testBinding.writeProjectTransaction(root, 'durable.txt', Buffer.from('published'), { mode: 'create', createParents: false })).toThrow('secure-fs durable directory sync failed');
|
|
438
|
+
expect(fs_1.default.readFileSync(path_1.default.join(root, 'durable.txt'), 'utf8')).toBe('published');
|
|
439
|
+
});
|
|
440
|
+
it('publishes an exact fenced replacement with fault injection disabled', () => {
|
|
441
|
+
const target = path_1.default.join(root, 'replace.json');
|
|
442
|
+
fs_1.default.writeFileSync(target, 'before');
|
|
443
|
+
const observed = testBinding.readRegularFile(target, 1024);
|
|
444
|
+
expect(() => testBinding.writeProjectTransaction(root, 'replace.json', Buffer.from('after'), {
|
|
445
|
+
mode: 'replace', expected: observed.bytes, expectedIdentity: observed.identity, createParents: false,
|
|
446
|
+
})).not.toThrow();
|
|
447
|
+
expect(fs_1.default.readFileSync(target, 'utf8')).toBe('after');
|
|
448
|
+
});
|
|
449
|
+
it('keeps the production prebuild free of the test-only control', () => {
|
|
450
|
+
if (!nativeFixtureAvailable)
|
|
451
|
+
return;
|
|
452
|
+
const production = require(path_1.default.join(__dirname, '../../../prebuilds', `${process.platform}-${process.arch}`, 'secure_fs.node'));
|
|
453
|
+
expect(production.setDirectoryFsyncFailureForTests).toBeUndefined();
|
|
454
|
+
});
|
|
455
|
+
});
|
|
402
456
|
nativeOnly('native secure-fs identity fence fixtures', () => {
|
|
403
457
|
let root;
|
|
404
458
|
let binding;
|
|
@@ -406,6 +460,11 @@ nativeOnly('native secure-fs identity fence fixtures', () => {
|
|
|
406
460
|
root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-secure-fs-identity-'));
|
|
407
461
|
binding = require(path_1.default.join(__dirname, '../../../prebuilds', `${process.platform}-${process.arch}`, 'secure_fs.node'));
|
|
408
462
|
});
|
|
463
|
+
it('performs a real artifact-backed publish when the CI prebuild is available', () => {
|
|
464
|
+
const target = path_1.default.join(root, 'durable.txt');
|
|
465
|
+
binding.writeProjectTransaction(root, 'durable.txt', Buffer.from('published'), { mode: 'create', createParents: false });
|
|
466
|
+
expect(fs_1.default.readFileSync(target, 'utf8')).toBe('published');
|
|
467
|
+
});
|
|
409
468
|
afterEach(() => { fs_1.default.rmSync(root, { recursive: true, force: true }); });
|
|
410
469
|
it('returns bytes with a canonical native identity from the same regular-file read', () => {
|
|
411
470
|
const target = path_1.default.join(root, 'sensors.json');
|
|
@@ -690,6 +749,7 @@ windowsOnly('native secure-fs Windows handle fixtures', () => {
|
|
|
690
749
|
fs.symlinkSync(workerData.outside, workerData.trusted, 'junction');
|
|
691
750
|
Atomics.add(state, 1, 1);
|
|
692
751
|
Atomics.notify(state, 1);
|
|
752
|
+
parentPort.postMessage('ready');
|
|
693
753
|
Atomics.wait(state, 0, 0, 1);
|
|
694
754
|
fs.rmSync(workerData.trusted);
|
|
695
755
|
fs.renameSync(workerData.parked, workerData.trusted);
|
|
@@ -701,13 +761,30 @@ windowsOnly('native secure-fs Windows handle fixtures', () => {
|
|
|
701
761
|
restore();
|
|
702
762
|
parentPort.postMessage('done');
|
|
703
763
|
`, { eval: true, workerData: { shared, trusted, parked, outside } });
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
764
|
+
let resolveReady;
|
|
765
|
+
let rejectReady;
|
|
766
|
+
let resolveDone;
|
|
767
|
+
let rejectDone;
|
|
768
|
+
const ready = new Promise((resolve, reject) => { resolveReady = resolve; rejectReady = reject; });
|
|
769
|
+
const done = new Promise((resolve, reject) => { resolveDone = resolve; rejectDone = reject; });
|
|
770
|
+
const readyTimeout = setTimeout(() => rejectReady(new Error('junction swap worker did not become ready')), 5_000);
|
|
771
|
+
worker.on('message', message => {
|
|
772
|
+
if (message === 'ready') {
|
|
773
|
+
clearTimeout(readyTimeout);
|
|
774
|
+
resolveReady();
|
|
775
|
+
}
|
|
776
|
+
if (message === 'done')
|
|
777
|
+
resolveDone();
|
|
778
|
+
});
|
|
779
|
+
worker.once('error', error => {
|
|
780
|
+
clearTimeout(readyTimeout);
|
|
781
|
+
const failure = error instanceof Error ? error : new Error('junction swap worker failed');
|
|
782
|
+
rejectReady(failure);
|
|
783
|
+
rejectDone(failure);
|
|
707
784
|
});
|
|
708
785
|
let published = 0;
|
|
709
786
|
try {
|
|
710
|
-
|
|
787
|
+
await ready;
|
|
711
788
|
expect(Atomics.load(state, 1)).toBeGreaterThan(0);
|
|
712
789
|
for (let attempt = 0; attempt < 500; attempt += 1) {
|
|
713
790
|
try {
|