Skip to content

Commit 2150109

Browse files
committed
refactor: make analysis states unrepresentable
1 parent 63b0b1c commit 2150109

96 files changed

Lines changed: 1229 additions & 889 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/verification/managed-conformance-manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"schema_version": 1,
3-
"manifest_id": "ecm_aba877529714e59795e8a7b51c5513b17081bd701cea1ffee31422a1d95dfa31",
3+
"manifest_id": "ecm_1fde266c466ffd6dde6f171e702f2929fc19ab8afb9958d3ae11a518133740da",
44
"verifier": {
55
"id": "rea-managed-conformance",
66
"version": "1"
@@ -14,7 +14,7 @@
1414
"skill_digests": [
1515
{
1616
"skill_id": "reverse-engineer-anything",
17-
"sha256": "43fccde70ab600dfc86785a37b414865cb8c0f2cb479eb4ed6c2a90998e0f6a6"
17+
"sha256": "44a7bc2732a693757d8c54a99362422111573fdb73bd61c562421c7787a87b1a"
1818
}
1919
],
2020
"claims": [

scripts/verify-managed-conformance.mjs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -344,11 +344,11 @@ try {
344344
assert.equal(reconstructionImport.value.confidence, "inferred");
345345
const runtimePlan = await planManagedRuntimeCorrelationEvidence(
346346
{
347-
policy: {
348-
enabled: true,
347+
policy: () => ({
348+
status: "enabled",
349349
roots: [workspace],
350350
executablePath: runtimeExecutable,
351-
},
351+
}),
352352
authority: runtimeAuthority.value,
353353
},
354354
{

src/application/ApplicationWorkflowEvidenceResolver.ts

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
type TraceApplicationFeatureRequest,
1313
type TraceJavaScriptSemanticsRequest,
1414
} from "../contracts/applicationWorkflowInputContracts.js";
15+
import type { Evidence } from "../domain/evidence.js";
1516
import { AnalysisInputError, type AnalysisError } from "../domain/errors.js";
1617
import { projectInputIssues } from "../domain/inputIssueProjection.js";
1718
import { compareApplicationVersionsInputSchema } from "../domain/javascriptApplicationVersionComparisonSchemas.js";
@@ -69,11 +70,7 @@ export const resolveTraceApplicationFeatureRequestValidated = (
6970
input: TraceApplicationFeatureRequest,
7071
lookup?: EvidenceLookup,
7172
): Result<TraceInput, AnalysisError> => {
72-
const application = graphEvidence(
73-
input.application,
74-
input.application_evidence_id,
75-
lookup,
76-
);
73+
const application = graphEvidence(input, lookup);
7774
if (!application.ok) return application;
7875
const native = resolveEvidenceReferences(
7976
lookup,
@@ -109,11 +106,7 @@ export const resolveTraceJavaScriptSemanticsRequestValidated = (
109106
input: TraceJavaScriptSemanticsRequest,
110107
lookup?: EvidenceLookup,
111108
): Result<SemanticTraceInput, AnalysisError> => {
112-
const application = graphEvidence(
113-
input.application,
114-
input.application_evidence_id,
115-
lookup,
116-
);
109+
const application = graphEvidence(input, lookup);
117110
if (!application.ok) return application;
118111
const raw = { application: application.value, query: input.query };
119112
const parsed = traceJavaScriptSemanticsInputSchema.safeParse(raw);
@@ -138,9 +131,19 @@ export const resolveCompareApplicationVersionsRequestValidated = (
138131
input: CompareApplicationVersionsRequest,
139132
lookup?: EvidenceLookup,
140133
): Result<ComparisonInput, AnalysisError> => {
141-
const left = graphEvidence(input.left, input.left_evidence_id, lookup);
134+
const left = graphEvidence(
135+
"left" in input
136+
? { application: input.left }
137+
: { application_evidence_id: input.left_evidence_id },
138+
lookup,
139+
);
142140
if (!left.ok) return left;
143-
const right = graphEvidence(input.right, input.right_evidence_id, lookup);
141+
const right = graphEvidence(
142+
"right" in input
143+
? { application: input.right }
144+
: { application_evidence_id: input.right_evidence_id },
145+
lookup,
146+
);
144147
if (!right.ok) return right;
145148
const leftNative = resolveEvidenceReferences(
146149
lookup,
@@ -190,11 +193,7 @@ export const resolveCompareSourceToBundleRequestValidated = (
190193
input: CompareSourceToBundleRequest,
191194
lookup?: EvidenceLookup,
192195
): Result<SourceToBundleInput, AnalysisError> => {
193-
const application = graphEvidence(
194-
input.application,
195-
input.application_evidence_id,
196-
lookup,
197-
);
196+
const application = graphEvidence(input, lookup);
198197
if (!application.ok) return application;
199198
const raw = {
200199
reference: input.reference,
@@ -226,9 +225,19 @@ export const resolveCompareJavaScriptExportShapesRequestValidated = (
226225
input: CompareJavaScriptExportShapesRequest,
227226
lookup?: EvidenceLookup,
228227
): Result<ExportShapeComparisonInput, AnalysisError> => {
229-
const left = graphEvidence(input.left, input.left_evidence_id, lookup);
228+
const left = graphEvidence(
229+
"left" in input
230+
? { application: input.left }
231+
: { application_evidence_id: input.left_evidence_id },
232+
lookup,
233+
);
230234
if (!left.ok) return left;
231-
const right = graphEvidence(input.right, input.right_evidence_id, lookup);
235+
const right = graphEvidence(
236+
"right" in input
237+
? { application: input.right }
238+
: { application_evidence_id: input.right_evidence_id },
239+
lookup,
240+
);
232241
if (!right.ok) return right;
233242
const raw = {
234243
left: left.value,
@@ -249,19 +258,15 @@ export const resolveCompareJavaScriptExportShapesRequestValidated = (
249258
};
250259

251260
const graphEvidence = (
252-
evidence:
253-
| TraceApplicationFeatureRequest["application"]
254-
| TraceJavaScriptSemanticsRequest["application"]
255-
| CompareApplicationVersionsRequest["left"]
256-
| CompareJavaScriptExportShapesRequest["left"]
257-
| CompareSourceToBundleRequest["application"],
258-
evidenceId: string | undefined,
261+
source:
262+
| { readonly application: Evidence }
263+
| { readonly application_evidence_id: string },
259264
lookup: EvidenceLookup | undefined,
260265
) => {
261-
if (evidence !== undefined) return ok(evidence);
266+
if ("application" in source) return ok(source.application);
262267
const resolved = resolveEvidenceReferences(
263268
lookup,
264-
evidenceId === undefined ? [] : [evidenceId],
269+
[source.application_evidence_id],
265270
APPLICATION_GRAPH_IDENTITIES,
266271
);
267272
return resolved.ok ? ok(resolved.value[0]) : resolved;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js";
2+
import type {
3+
ArtifactIntegrityPolicy,
4+
ArtifactNativeMountPolicy,
5+
} from "./types.js";
6+
7+
/** Parsed caller intent for integrity mismatch handling. */
8+
export type ArtifactIntegrityIntent =
9+
| { readonly mode: "fail" }
10+
| {
11+
readonly mode: "record-and-continue";
12+
readonly maxMismatches: number;
13+
};
14+
15+
/** Resolve caller approval and operator policy before native reader selection. */
16+
export const resolveNativeMountPolicy = (
17+
approved: boolean,
18+
enabled: boolean,
19+
): ArtifactNativeMountPolicy => {
20+
if (!approved) return { status: "disabled" };
21+
if (!enabled)
22+
throw new ArtifactReaderFailure(
23+
"unavailable",
24+
"Native DMG mounting is disabled by operator policy",
25+
);
26+
return { status: "approved" };
27+
};
28+
29+
/** Resolve parsed caller intent and operator policy before artifact scanning. */
30+
export const resolveArtifactIntegrityPolicy = (
31+
intent: ArtifactIntegrityIntent,
32+
enabled: boolean,
33+
): ArtifactIntegrityPolicy => {
34+
if (intent.mode === "fail") return intent;
35+
if (!enabled)
36+
throw new ArtifactReaderFailure(
37+
"policy",
38+
"Integrity continuation requires explicit approval and operator policy",
39+
);
40+
return intent;
41+
};

src/application/ArtifactInventory/reader.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import { AsarArtifactReader } from "../../artifacts/AsarArtifactReader.js";
2-
import {
3-
ArtifactReaderFailure,
4-
type ArtifactReader,
5-
} from "../../artifacts/ArtifactReader.js";
2+
import type { ArtifactReader } from "../../artifacts/ArtifactReader.js";
63
import { DirectoryArtifactReader } from "../../artifacts/DirectoryArtifactReader.js";
74
import { MachOSliceArtifactReader } from "../../artifacts/MachOSliceArtifactReader.js";
85
import { NativeDmgArtifactReader } from "../../artifacts/NativeDmgArtifactReader.js";
@@ -32,12 +29,7 @@ export const createReader = async (
3229
? new MachOSliceArtifactReader(path)
3330
: undefined;
3431
case "dmg":
35-
if (!nativeMount.nativeMountApproved) return undefined;
36-
if (!nativeMount.nativeMountEnabled)
37-
throw new ArtifactReaderFailure(
38-
"unavailable",
39-
"Native DMG mounting is disabled by operator policy",
40-
);
32+
if (nativeMount.status === "disabled") return undefined;
4133
return NativeDmgArtifactReader.create(path, signal);
4234
default:
4335
return undefined;

src/application/ArtifactInventory/scanCanonical.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,6 @@ export const scanCanonicalArtifactInventory = async (
4444
options: ArtifactInventoryOptions = {},
4545
): Promise<ArtifactInventorySnapshot> => {
4646
const integrity = options.integrity ?? STRICT_INTEGRITY_POLICY;
47-
if (
48-
integrity.mode === "record-and-continue" &&
49-
(!integrity.approved || !integrity.enabled)
50-
)
51-
throw new ArtifactReaderFailure(
52-
"policy",
53-
"Integrity continuation requires explicit approval and operator policy",
54-
);
5547
const metadata = await lstat(path);
5648
const rootFormat = await classifyRoot(path, metadata.isDirectory());
5749
const rootDigest = metadata.isDirectory()

src/application/ArtifactInventory/scanReader.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -166,19 +166,19 @@ const digestArtifactEntry = async (
166166
context.totalBytes += digest.bytes;
167167
const mismatched =
168168
entry.declaredSha256 !== null && entry.declaredSha256 !== digest.sha256;
169-
if (mismatched && context.integrity.mode === "fail")
170-
throw new ArtifactReaderFailure(
171-
"integrity",
172-
`Artifact integrity metadata disagrees with content: ${logicalPath}`,
173-
undefined,
174-
{
175-
logicalPath,
176-
declaredSha256: entry.declaredSha256,
177-
calculatedSha256: digest.sha256,
178-
unpacked: entry.unpacked,
179-
},
180-
);
181169
if (mismatched && entry.declaredSha256 !== null) {
170+
if (context.integrity.mode === "fail")
171+
throw new ArtifactReaderFailure(
172+
"integrity",
173+
`Artifact integrity metadata disagrees with content: ${logicalPath}`,
174+
undefined,
175+
{
176+
logicalPath,
177+
declaredSha256: entry.declaredSha256,
178+
calculatedSha256: digest.sha256,
179+
unpacked: entry.unpacked,
180+
},
181+
);
182182
if (context.pendingContradictions.length >= context.integrity.maxMismatches)
183183
throw new ArtifactReaderFailure(
184184
"limit",

src/application/ArtifactInventory/types.ts

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,25 @@ export interface InventoryPageInput {
1313
readonly edgeLimit: number;
1414
}
1515

16-
/** Per-request authority combined with operator-owned native mount policy. */
17-
export interface ArtifactNativeMountPolicy {
18-
readonly nativeMountApproved: boolean;
19-
readonly nativeMountEnabled: boolean;
20-
}
16+
/** Resolved native mount authority admitted to the artifact reader. */
17+
export type ArtifactNativeMountPolicy =
18+
| { readonly status: "disabled" }
19+
| { readonly status: "approved" };
2120

2221
export const NATIVE_MOUNT_DISABLED: ArtifactNativeMountPolicy = {
23-
nativeMountApproved: false,
24-
nativeMountEnabled: false,
22+
status: "disabled",
2523
};
2624

27-
/** Explicit caller approval bounded by operator-owned integrity policy. */
28-
export interface ArtifactIntegrityPolicy {
29-
readonly mode: "fail" | "record-and-continue";
30-
readonly approved: boolean;
31-
readonly enabled: boolean;
32-
readonly maxMismatches: number;
33-
}
25+
/** Resolved integrity behavior admitted to the artifact scanner. */
26+
export type ArtifactIntegrityPolicy =
27+
| { readonly mode: "fail" }
28+
| {
29+
readonly mode: "record-and-continue";
30+
readonly maxMismatches: number;
31+
};
3432

3533
export const STRICT_INTEGRITY_POLICY: ArtifactIntegrityPolicy = {
3634
mode: "fail",
37-
approved: false,
38-
enabled: false,
39-
maxMismatches: 1,
4035
};
4136

4237
/** Options shared by artifact inventory scans. */

src/application/BinarySession.ts

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,9 @@ export class BinarySession
7474
#transition: Promise<void> = Promise.resolve();
7575
readonly #calls = new Set<Promise<unknown>>();
7676
readonly #providerRouter: SessionProviderRouter;
77-
readonly #runtimeAvailability = new Map<
77+
readonly #runtimeUnavailability = new Map<
7878
string,
79-
{ readonly available: boolean; readonly reason: string | null }
79+
{ readonly reason: string }
8080
>();
8181
readonly #availabilityListeners = new Set<() => void | Promise<void>>();
8282

@@ -272,7 +272,7 @@ export class BinarySession
272272
target: this.#active?.target,
273273
route: this.#currentRoute(),
274274
router: this.#providerRouter,
275-
runtimeAvailability: this.#runtimeAvailability,
275+
runtimeUnavailability: this.#runtimeUnavailability,
276276
runId: this.#active?.runId,
277277
runtimeLineageSnapshots:
278278
this.#active?.client.runtimeLineageSnapshots?.() ?? [],
@@ -344,12 +344,12 @@ export class BinarySession
344344
result: Result<AnalysisExecution, AnalysisError>,
345345
): void {
346346
if (result.ok) {
347-
if (this.#setRuntimeAvailability(operation, true, null))
347+
if (this.#markRuntimeAvailable(operation))
348348
this.#emitAvailabilityChanged();
349349
return;
350350
}
351351
if (result.error._tag === "AnalysisCapabilityUnavailableError") {
352-
if (this.#setRuntimeAvailability(operation, false, result.error.message))
352+
if (this.#markRuntimeUnavailable(operation, result.error.message))
353353
this.#emitAvailabilityChanged();
354354
return;
355355
}
@@ -367,32 +367,28 @@ export class BinarySession
367367
for (const descriptor of capabilities?.values() ?? [])
368368
if (providerId !== undefined && descriptor.provider.id === providerId)
369369
changed =
370-
this.#setRuntimeAvailability(
370+
this.#markRuntimeUnavailable(
371371
descriptor.operation,
372-
false,
373372
"Provider became unavailable during this session.",
374373
) || changed;
375374
if (changed) this.#emitAvailabilityChanged();
376375
}
377376
}
378377

379-
#setRuntimeAvailability(
380-
operation: string,
381-
available: boolean,
382-
reason: string | null,
383-
): boolean {
384-
const current = this.#runtimeAvailability.get(operation);
385-
if (current?.available === available && current.reason === reason)
386-
return false;
387-
if (available && current === undefined) return false;
388-
if (available) this.#runtimeAvailability.delete(operation);
389-
else this.#runtimeAvailability.set(operation, { available, reason });
378+
#markRuntimeAvailable(operation: string): boolean {
379+
return this.#runtimeUnavailability.delete(operation);
380+
}
381+
382+
#markRuntimeUnavailable(operation: string, reason: string): boolean {
383+
const current = this.#runtimeUnavailability.get(operation);
384+
if (current?.reason === reason) return false;
385+
this.#runtimeUnavailability.set(operation, { reason });
390386
return true;
391387
}
392388

393389
#clearRuntimeAvailability(): void {
394-
if (this.#runtimeAvailability.size === 0) return;
395-
this.#runtimeAvailability.clear();
390+
if (this.#runtimeUnavailability.size === 0) return;
391+
this.#runtimeUnavailability.clear();
396392
this.#emitAvailabilityChanged();
397393
}
398394

0 commit comments

Comments
 (0)