refactor: return complete analysis results inline

This commit is contained in:
morluto
2026-09-29 08:07:24 +08:00
parent 07ff8b30ee
commit a2a3243ea8
127 changed files with 402 additions and 1508 deletions
@@ -218,7 +218,7 @@ declaring type + normalized CLI signature
build-local metadata token
RVA/file extent when present
raw CIL byte length + SHA-256 when the body is admitted
member-result schema version + decoded-instruction-tuple SHA-256 when CIL is valid
decoded-instruction-tuple SHA-256 when CIL is valid
separate max-stack, locals-token, and exception-region observations
```
@@ -232,9 +232,8 @@ The caller-visible field remains named `normalized_il_sha256`, but its shipped
v1 meaning is deliberately narrower than a canonical semantic CIL identity.
For a completely decoded method, REA serializes the instruction-order array of
`[opcode, operand_kind, operand]` tuples with JavaScript `JSON.stringify`,
encodes that text as UTF-8, and reports its lowercase SHA-256. The member
result's `schema_version: 1` scopes this convention; there is no independent
normalized-CIL algorithm-version field in v1.
encodes that text as UTF-8, and reports its lowercase SHA-256. The tuple
projection is defined here; the member result carries no schema-version field.
Operands retain the current decoder projection: metadata and user-string
operands are build-local lowercase hexadecimal tokens, scalar branch operands
@@ -258,8 +257,8 @@ normalized-CIL schema.
Metadata tokens are reported because they are precise coordinates inside one
module. Cross-version identity never consists of a token alone. A structural
match cites both build commitments, both local tokens, the v1 tuple inputs, the
member-result schema version, and all competing candidates. V1 cannot cite a
match cites both build commitments, both local tokens, the v1 tuple inputs, and
all competing candidates. V1 cannot cite a
separate matching-algorithm version because none is exposed.
### 5. Separate five evidence layers
+3 -4
View File
@@ -121,8 +121,8 @@ boundaries:
The normalized field is available only when the entire CIL stream decodes with
status `present`. A partial, malformed, absent, or over-limit body reports
`null`. The containing member result's `schema_version: 1` scopes the tuple
format; v1 does not expose a separate algorithm-version field.
`null`. The tuple format is defined here and does not add a schema-version field
to the member result.
Tuple operands use these exact projections:
@@ -220,7 +220,7 @@ Cross-version matching is two-stage:
1. Exact identity requires compatible artifact/module commitments and exact
raw CIL identity; v1 does not expose a standalone complete-method-body
digest.
2. Structural identity compares schema-versioned signatures, the limited v1
2. Structural identity compares normalized signatures, the limited v1
decoded-CIL tuple fingerprint, constants, and bounded shape context. It
reports all candidates at the winning score and remains ambiguous when the
evidence does not distinguish them. The v1 digest does not itself remap
@@ -327,7 +327,6 @@ absolute or relative to the manifest file. A compact manifest contains:
```json
{
"schema_version": 1,
"label": "operator-local osu!stable semantic slice",
"target": {
"path": "./osu!.exe",
+4 -8
View File
@@ -39,9 +39,7 @@ rea build-reconstruction-obligation-ledger \
"manifest": {
"bindings": [],
"contradictions": []
},
"limits": { "max_obligations": 100 },
"page": { "offset": 0, "limit": 50 }
}
}' \
--json
```
@@ -322,8 +320,6 @@ close a `packaged-process` obligation. A static-only source cannot close a
runtime or process claim. Marking a claim `blocked` or `out-of-scope` records the
disposition but does not silently count it as verified.
Pagination changes only `page` and the returned `obligations` slice. Every page
retains the same `ledger_id`, `closure_digest`, full-ledger summaries, reports,
ownership graph, dependency graph, Evidence links, and limitations. Consumers
should follow `next_offset` until it is `null`, and should reject pages whose
identity or closure digest changes mid-read.
The tool returns every obligation inline. Its `ledger_id` and `closure_digest`
identify the complete result, including summaries, reports, ownership and
dependency graphs, Evidence links, and limitations.
@@ -1,103 +1,102 @@
{
"schema_version": 1,
"ledger_id": "ecl_2d5c1c83c1fd56615a663bcb3a432b7023afa316133b744c64dcaa659cab53c0",
"ledger_id": "ecl_578e5853b332ae050e69dc1a2c72fc98be527371311d6a98a9ae3bf28a24fa6c",
"records": [
{
"claim_id": "managed.application-graph-projection",
"status": "pass",
"evidence_ids": [
"ev_0c73f33f51048a7c7cc5d908cb951512ee9f3d9dcf4124f0f0eb2c4a88ceb7cc"
"ev_a09c9014515baea9a709c1d5c0ae32d343a1f1b6e4da498ce5afe14eaab0ddbb"
]
},
{
"claim_id": "managed.decompiler-reconstruction-import",
"status": "pass",
"evidence_ids": [
"ev_0504a7fc4cc8f77ca07a3aa59d62830b5769c7b41e7518677771fe7aa54c7b5d"
"ev_14052dcba244a91019c042feae78440d800570dde163c991931d9c09b7e6bbdd"
]
},
{
"claim_id": "managed.dotnet-framework-x86-pinvoke",
"status": "pass",
"evidence_ids": [
"ev_9b63cdeef9d48e567dc8f4de9fe6b424e94929ac1f17aafd0ef1807775f72d96"
"ev_c11aa40e3fbc2de8bb11d6b5783ee3a74d4d79fe16ea7f6ddd85d4b253c6d769"
]
},
{
"claim_id": "managed.ilspy-reconstruction-oracle",
"status": "unsupported",
"evidence_ids": [
"ev_4e3fbc7203c29aab5d9119332a09c14e285e85007b3d7fc7433880b39a507435"
"ev_76ca183c167d63e6c58b65e2f9b3bef3e569669dc66abb0b8b850948878a70e3"
]
},
{
"claim_id": "managed.malformed-metadata",
"status": "pass",
"evidence_ids": [
"ev_dbd885adc65f9cc8abacb04642db609d2180b0af71adc0b0732719f06a057063"
"ev_99e1185da1fcbf1330b4b86beca27e0abeef10f95870b1982da39827278e16e8"
]
},
{
"claim_id": "managed.managed-native-verification",
"status": "pass",
"evidence_ids": [
"ev_e7841bf63d7b83804987d21f4f432752870ff7338862360bbcf54653eb782731"
"ev_9d1c5bc641c0da1551c8fbae4507d7719847cfb40edf73c13e2bdf5fb670ec9e"
]
},
{
"claim_id": "managed.modern-dotnet-anycpu",
"status": "pass",
"evidence_ids": [
"ev_9593b026617002c64d0618ec079d42dbc0ddeef7cd53f2f8b2c8fca9ffebb2a9"
"ev_7f959cb9642a2c52da0cce0d3c9c888b889240ee7697c5fdb5390d7578634782"
]
},
{
"claim_id": "managed.mvid-and-token-drift",
"status": "pass",
"evidence_ids": [
"ev_ba32694a35810acd211745efdf41c88887f4931efb6027e876eea3e8e506aee0"
"ev_dd6c3e3fbe115f1b9b38ee06f720d5efb7dd6ef693a1b9555e9ff73babdaea9f"
]
},
{
"claim_id": "managed.not-managed",
"status": "pass",
"evidence_ids": [
"ev_ac700cf6c8efdda7f689b29a9c3d5a679ecb24893f93bb1c76e50e23b2b9dcbb"
"ev_ca183a8f4159136df29d9d5e44a88239c996d6e758b71ad023289871875a9367"
]
},
{
"claim_id": "managed.operator-local-manifest",
"status": "unsupported",
"evidence_ids": [
"ev_f96f07bc28f2e6da5a71597f9a38f5eaa7cde0217f0df10d6ef6d655937fc1a2"
"ev_4173365e113f71b28fd1f3f1c583557a487e36ec6a6d0e63b1249eb7a4334b8c"
]
},
{
"claim_id": "managed.operator-manifest-self-test",
"status": "pass",
"evidence_ids": [
"ev_ed0f3ff1749f0927d493609423a366ec6a432f016c767aeeedd9b4a520672eaf"
"ev_063c68b649379811e56c7788ab6b01891f1dd78abfe3008c6bf06a1c0727a095"
]
},
{
"claim_id": "managed.runtime-correlation-admission-plan",
"status": "pass",
"evidence_ids": [
"ev_24a622e16a885e2d21025f51c3f4b5900719c21d9f0d04ba1d55f6093a702c20"
"ev_530ff114b2f05f85b6baeaa56df60a60a927f1de3b07bbbe86aa462faad71772"
]
},
{
"claim_id": "managed.unicode-obfuscated-identifiers",
"status": "pass",
"evidence_ids": [
"ev_2e9bc5213917635c3eb3d094449adf088c450c07d078dcb26312182c04966bc9"
"ev_17f9013d5ce058be847c1f3efec19688c268fa2feecf974bbc18428cd87144e5"
]
},
{
"claim_id": "managed.x64-ready-to-run-native-body",
"status": "pass",
"evidence_ids": [
"ev_bbf3dd8daec6d46d16ac7befc3875572e7103b27b815f81903e3dca61880c70b"
"ev_60dc353ad66b83ee50163ab55fb0f4c7caefd06e08ccc2e0ebb5d2a7a23cb55b"
]
}
],
@@ -1,6 +1,5 @@
{
"schema_version": 1,
"manifest_id": "ecm_3a42d32a96315338e06700b64afc0367603e2c5050b1ec0bf1ce012e227b4df3",
"manifest_id": "ecm_0f98c38501954b90ff9f949f1ee9a0b550061347cf92780cfb1b14ccef5131f7",
"verifier": {
"id": "rea-managed-conformance",
"version": "1"
@@ -14,7 +13,7 @@
"skill_digests": [
{
"skill_id": "reverse-engineer-anything",
"sha256": "89b09782340415790ecf161a5bc33f60e1a23cd66e0f4c29dd26e959b2baa0a9"
"sha256": "b78dbf9a5f0db15415f03dccab71bfb2d133900ab061e37825f55f3b7f0f80bc"
}
],
"claims": [
@@ -30,8 +29,7 @@
"provider": {
"id": "rea-managed-conformance",
"version": "1"
},
"result_schema_version": 1
}
},
{
"claim_id": "managed.decompiler-reconstruction-import",
@@ -45,8 +43,7 @@
"provider": {
"id": "rea-managed-conformance",
"version": "1"
},
"result_schema_version": 1
}
},
{
"claim_id": "managed.dotnet-framework-x86-pinvoke",
@@ -60,8 +57,7 @@
"provider": {
"id": "rea-managed-conformance",
"version": "1"
},
"result_schema_version": 1
}
},
{
"claim_id": "managed.ilspy-reconstruction-oracle",
@@ -73,8 +69,7 @@
"provider": {
"id": "rea-managed-conformance",
"version": "1"
},
"result_schema_version": 1
}
},
{
"claim_id": "managed.malformed-metadata",
@@ -88,8 +83,7 @@
"provider": {
"id": "rea-managed-conformance",
"version": "1"
},
"result_schema_version": 1
}
},
{
"claim_id": "managed.managed-native-verification",
@@ -104,8 +98,7 @@
"provider": {
"id": "rea-managed-conformance",
"version": "1"
},
"result_schema_version": 1
}
},
{
"claim_id": "managed.modern-dotnet-anycpu",
@@ -119,8 +112,7 @@
"provider": {
"id": "rea-managed-conformance",
"version": "1"
},
"result_schema_version": 1
}
},
{
"claim_id": "managed.mvid-and-token-drift",
@@ -135,8 +127,7 @@
"provider": {
"id": "rea-managed-conformance",
"version": "1"
},
"result_schema_version": 1
}
},
{
"claim_id": "managed.not-managed",
@@ -150,8 +141,7 @@
"provider": {
"id": "rea-managed-conformance",
"version": "1"
},
"result_schema_version": 1
}
},
{
"claim_id": "managed.operator-local-manifest",
@@ -163,8 +153,7 @@
"provider": {
"id": "rea-managed-conformance",
"version": "1"
},
"result_schema_version": 1
}
},
{
"claim_id": "managed.operator-manifest-self-test",
@@ -178,8 +167,7 @@
"provider": {
"id": "rea-managed-conformance",
"version": "1"
},
"result_schema_version": 1
}
},
{
"claim_id": "managed.runtime-correlation-admission-plan",
@@ -193,8 +181,7 @@
"provider": {
"id": "rea-managed-conformance",
"version": "1"
},
"result_schema_version": 1
}
},
{
"claim_id": "managed.unicode-obfuscated-identifiers",
@@ -208,8 +195,7 @@
"provider": {
"id": "rea-managed-conformance",
"version": "1"
},
"result_schema_version": 1
}
},
{
"claim_id": "managed.x64-ready-to-run-native-body",
@@ -223,8 +209,7 @@
"provider": {
"id": "rea-managed-conformance",
"version": "1"
},
"result_schema_version": 1
}
}
]
}
+2 -2
View File
@@ -95,8 +95,8 @@ and no verified no-follow equivalent for every path component.
Linux continues to use a mode-restricted Unix socket. Windows uses a Java
listener bound to `127.0.0.1` on an ephemeral port. The bridge atomically
publishes a bounded endpoint record containing only schema version, literal
loopback host, and port. The random 256-bit request token remains only in the
publishes a bounded endpoint record containing only the literal loopback host
and port. The random 256-bit request token remains only in the
ephemeral session descriptor and every request is authenticated.
`analyzeHeadless.bat` is invoked through an absolute `cmd.exe` with a fixed
-1
View File
@@ -291,7 +291,6 @@ def diagnostic(
(0, False, "") if capture is None else capture.snapshot()
)
return {
"schema_version": 1,
"component": "hopper_private_display",
"operation": operation,
"status": status,
@@ -42,7 +42,6 @@ const duplicateOriginStorage = (origin) => ({
/** Build the source-owned full-capture browser scenario. */
export function browserScenario(browser, origin) {
return browserScenarioSchema.parse({
schema_version: 1,
browser,
start_url: { url: `${origin}/app`, query: [] },
allowed_origins: [origin],
@@ -22,7 +22,6 @@ const completionClaim = ({
parameters: {
scenario_id: scenarioId,
scenario_version: 1,
result_schema_version: 1,
artifact_sha256s: artifactSha256s,
},
result: { status },
@@ -37,7 +36,6 @@ const completionClaim = ({
id: COMPLETION_PROVIDER.id,
version: COMPLETION_PROVIDER.version,
},
result_schema_version: 1,
status,
evidence_ids: [evidence.evidence_id],
};
@@ -151,7 +149,6 @@ const optionalClaims = ({ operatorManifest, ilspyOracle }) => [
/** Project completed managed assertions into the standard verifier report. */
export const createManagedCompletionReport = (input, verifierRun) => ({
schema_version: 1,
verifier: {
id: COMPLETION_PROVIDER.id,
version: COMPLETION_PROVIDER.version,
@@ -1,14 +1,9 @@
export function parseManagedAppManifest(rawManifest) {
const manifest = object(rawManifest, "manifest");
ensure(
manifest.schema_version === 1,
"manifest.schema_version must be exactly 1",
);
const target = object(manifest.target, "manifest.target");
const methods = array(manifest.methods, "manifest.methods");
ensure(manifest.methods.length > 0, "manifest.methods must not be empty");
return {
schema_version: 1,
label: optionalString(manifest.label, "manifest.label"),
target: {
path: string(target.path, "manifest.target.path"),
@@ -43,7 +43,6 @@ async function runManagedAppManifestSelfTest(context) {
assert.ok(method);
const summary = await context.verifyManagedAppManifest(
{
schema_version: 1,
label: "source-owned manifest verifier self-test",
target: {
path: sample.path,
-2
View File
@@ -1,5 +1,4 @@
export interface VerifierRunStart {
readonly schema_version: 1;
readonly run_id: string;
readonly verifier_pid: number;
readonly parent_pid: number;
@@ -14,7 +13,6 @@ export interface VerifierRunDescendant {
export type VerifierProcessLineage =
| {
readonly status: "verified";
readonly schema_version: 1;
readonly observed_at: string;
readonly launcher_pid: number;
readonly launcher_parent_pid: number;
-2
View File
@@ -11,7 +11,6 @@ export const createVerifierRun = () => {
return activeVerifierRun;
}
activeVerifierRun = Object.freeze({
schema_version: 1,
run_id: randomUUID(),
verifier_pid: process.pid,
parent_pid: process.ppid,
@@ -85,7 +84,6 @@ const observeVerifierLineage = async (run, observedAt) => {
}
return {
status: "verified",
schema_version: 1,
observed_at: observedAt,
launcher_pid: run.verifier_pid,
launcher_parent_pid: run.parent_pid,
-1
View File
@@ -5,7 +5,6 @@ const REQUIRED_HELP_COMMANDS = [
"upgrade",
"inventory-artifact",
"extract-artifact",
"investigate-versions",
"import-reference-source",
"list-browser-targets",
"inspect-web-page",
-1
View File
@@ -20,7 +20,6 @@ const applicationRoot =
const applicationPath =
process.env.REA_ELECTRON_APPLICATION_PATH ?? join(applicationRoot, "main.js");
const input = electronActiveObservationInputSchema.parse({
schema_version: 1,
executable_path: executable,
application_path: applicationPath,
application_root: applicationRoot,
@@ -71,7 +71,6 @@ try {
throw new Error("Real Node Inspector capture was incomplete");
process.stdout.write(
`${JSON.stringify({
schema_version: 1,
status: "pass",
provider: observed.provider.id,
target_id: target.target_id,
@@ -157,7 +157,6 @@ try {
process.stdout.write(
`${JSON.stringify({
schema_version: 1,
status: cliReport.status,
report_id: cliReport.report_id,
report_digest: cliReport.report_digest,
+1 -1
View File
@@ -4,7 +4,7 @@ description: Reverse engineer native, managed, Electron/JavaScript, packaged, an
metadata:
version: "23"
tool_count: 115
catalog_digest: "94f7cd1ac629a6bbffa77ba3b3d3f2e208e15df821bc0b14db4f81692d6f8eaa"
catalog_digest: "85d3e5545b3e01eb22b67a1794fdcb737654a1abbea803e1f2445815ee4f17ff"
---
# REA
@@ -396,8 +396,6 @@ const capability = (provider: ProviderIdentity): CapabilityDescriptor => ({
operation: "address_name",
available: true,
reason: null,
pagination: "none",
exhaustive: true,
effects: {
mutatesArtifact: false,
launchesProcess: true,
@@ -407,7 +405,6 @@ const capability = (provider: ProviderIdentity): CapabilityDescriptor => ({
changesPermissions: false,
requiresRoot: false,
},
limits: { maxResults: null, maxPayloadBytes: 1_000, timeoutMs: 1_000 },
limitations: [],
});
@@ -161,7 +161,6 @@ export const resolveCompareApplicationVersionsRequestValidated = (
...input.right_native_observations,
...rightNative.value,
],
limits: input.limits,
...(input.unknown_registry_approved === undefined
? {}
: { unknown_registry_approved: input.unknown_registry_approved }),
@@ -241,7 +240,6 @@ export const resolveCompareJavaScriptExportShapesRequestValidated = (
left_export_name: input.left_export_name,
right_module_path: input.right_module_path,
right_export_name: input.right_export_name,
limits: input.limits,
...(input.unknown_registry_approved === undefined
? {}
: { unknown_registry_approved: input.unknown_registry_approved }),
+1 -7
View File
@@ -53,8 +53,6 @@ export const exportEvidenceBundleCommand = async (
export const compareEvidenceBundlesCommand = async (input: {
readonly leftPath: string;
readonly rightPath: string;
readonly offset: number;
readonly limit: number;
readonly policy: EvidenceFilePolicy;
}): Promise<Result<JsonValue, AnalysisError>> => {
const [left, right] = await Promise.all([
@@ -64,11 +62,7 @@ export const compareEvidenceBundlesCommand = async (input: {
if (!left.ok) return left;
if (!right.ok) return right;
try {
return ok(
jsonValueSchema.parse(
compareBundles(left.value, right.value, [], input.offset, input.limit),
),
);
return ok(jsonValueSchema.parse(compareBundles(left.value, right.value)));
} catch (cause: unknown) {
return err(
new EvidenceIntegrityError("Evidence bundle comparison failed", {
@@ -119,9 +119,9 @@ export const compareApplicationVersionsEvidenceValidated = (
rootArtifactSha256: right.rootArtifactSha256,
graph: right.graph,
},
limits: input.limits,
leftNativeEvidence: leftNative,
rightNativeEvidence: rightNative,
limits: input.limits,
});
return ok(
createApplicationVersionComparisonEvidence(
@@ -214,7 +214,6 @@ export const compareJavaScriptExportShapesEvidenceValidated = (
modulePath: input.right_module_path,
exportName: input.right_export_name,
},
limits: input.limits,
});
return ok(
createJavaScriptExportShapeComparisonEvidence(
@@ -225,7 +224,6 @@ export const compareJavaScriptExportShapesEvidenceValidated = (
left_export_name: input.left_export_name,
right_module_path: input.right_module_path,
right_export_name: input.right_export_name,
limits: input.limits,
},
result,
),
@@ -125,7 +125,6 @@ export const prepareReplayPlan = async (
: prepareSide(input.right, input.limits.module_bytes, host),
]);
const withoutDigest = {
schema_version: 1 as const,
policy_version: REPLAY_POLICY_VERSION,
policy_sha256: digestJson(REPLAY_POLICY_DOCUMENT),
network: "none" as const,
@@ -310,7 +310,6 @@ const applyReproducerExport = async (
return executed;
const manifest = {
schema_version: 1,
plan: prepared.publicPlan,
result: executed,
sources: exportContext.includeSources
@@ -54,7 +54,7 @@ it("projects closure and direct interprocedural flow without execution", () => {
);
});
it("keeps dynamic calls explicit and returns bounded deterministic pages", () => {
it("keeps dynamic calls explicit and returns complete deterministic results", () => {
const graph = graphFor(`
const handlers = { ready() { return 1; } };
const key = process.argv[2];
@@ -73,21 +73,11 @@ it("keeps dynamic calls explicit and returns bounded deterministic pages", () =>
seed: { kind: "semantic-node", node_id: seedNodeId },
direction: "forward-influence",
include_ambiguous_dynamic_edges: true,
limits: {
max_seed_matches: 25,
max_nodes: 2_000,
max_edges: 4_000,
max_depth: 12,
max_functions: 500,
max_modules: 500,
page_size: 1,
},
});
const repeated = queryJavaScriptSemanticGraph(graph, {
seed: { kind: "semantic-node", node_id: seedNodeId },
direction: "forward-influence",
include_ambiguous_dynamic_edges: true,
limits: first.applied_limits,
});
expect(repeated).toEqual(first);
});
@@ -8,7 +8,6 @@ import {
import type { Evidence } from "../domain/evidence.js";
import { jsonValueSchema } from "../domain/jsonValue.js";
import { queryJavaScriptSemanticGraph } from "../domain/javascriptSemanticQuery.js";
import { JavaScriptSemanticQueryCursorError } from "../domain/javascriptSemanticQueryIdentity.js";
import {
javaScriptSemanticTraceResultSchema,
traceJavaScriptSemanticsInputSchema,
@@ -55,16 +54,6 @@ export const traceJavaScriptSemanticsEvidenceValidated = (
),
);
} catch (cause: unknown) {
if (cause instanceof JavaScriptSemanticQueryCursorError)
return err(
new AnalysisInputError(OPERATION, undefined, [
{
path: ["query", "cursor"],
reason: "invalid_value",
expected: "a cursor returned by the same graph, query, and limits",
},
]),
);
return err(
new AnalysisProtocolError("JavaScript semantic trace failed", { cause }),
);
@@ -284,7 +284,6 @@ const createCharacterizationPlan = (
replayPlan: ReplayPlan,
): RuntimeCharacterizationPlan =>
createRuntimeCharacterizationPlan({
schema_version: 1,
preparation_sha256: digestPreparation(
instrumentation.manifest.instrumented_sha256,
replayPlan.plan_digest,
@@ -11,9 +11,9 @@ import { createEvidenceBundle } from "../domain/evidenceBundle.js";
import { jsonValueSchema } from "../domain/jsonValue.js";
import { processCaptureSchema } from "../domain/processCapture.js";
import {
reconstructionObligationLedgerPageSchema,
reconstructionObligationLedgerSchema,
type ReconstructionObligationLedgerInput,
type ReconstructionObligationLedgerPage,
type ReconstructionObligationLedger,
type ReviewedReconstructionObligation,
} from "../domain/reconstructionObligationLedgerSchemas.js";
@@ -81,26 +81,25 @@ const request = (
evidence_bundle: createEvidenceBundle(records),
reviewed_obligations: [],
manifest: { bindings: [], contradictions: [] },
page: { offset: 0, limit: 50 },
...overrides,
});
const build = (
input: ReconstructionObligationLedgerInput,
): ReconstructionObligationLedgerPage => {
): ReconstructionObligationLedger => {
const parsed = resolveReconstructionObligationLedgerRequest(input);
if (!parsed.ok) throw parsed.error;
const result = buildReconstructionObligationLedgerEvidenceValidated(
parsed.value,
);
if (!result.ok) throw result.error;
return reconstructionObligationLedgerPageSchema.parse(
return reconstructionObligationLedgerSchema.parse(
result.value.normalized_result,
);
};
const originalCases = (
obligation: ReconstructionObligationLedgerPage["obligations"][number],
obligation: ReconstructionObligationLedger["obligations"][number],
evidenceId: string,
) =>
obligation.required_case_kinds.map((caseKind) => ({
@@ -11,10 +11,10 @@ import { createEvidenceBundle } from "../domain/evidenceBundle.js";
import { jsonValueSchema } from "../domain/jsonValue.js";
import { processCaptureSchema } from "../domain/processCapture.js";
import {
reconstructionObligationLedgerPageSchema,
reconstructionObligationLedgerSchema,
serializeReconstructionObligationLedger,
type ReconstructionObligationLedgerInput,
type ReconstructionObligationLedgerPage,
type ReconstructionObligationLedger,
type ReviewedReconstructionObligation,
} from "../domain/reconstructionObligationLedgerSchemas.js";
@@ -82,36 +82,25 @@ const request = (
evidence_bundle: createEvidenceBundle(records),
reviewed_obligations: [],
manifest: { bindings: [], contradictions: [] },
page: { offset: 0, limit: 50 },
...overrides,
});
const build = (
input: ReconstructionObligationLedgerInput,
): ReconstructionObligationLedgerPage => {
): ReconstructionObligationLedger => {
const parsed = resolveReconstructionObligationLedgerRequest(input);
if (!parsed.ok) throw parsed.error;
const result = buildReconstructionObligationLedgerEvidenceValidated(
parsed.value,
);
if (!result.ok) throw result.error;
return reconstructionObligationLedgerPageSchema.parse(
return reconstructionObligationLedgerSchema.parse(
result.value.normalized_result,
);
};
const completeLedger = (
page: ReconstructionObligationLedgerPage,
obligations: ReconstructionObligationLedgerPage["obligations"],
) => {
const { page: pagination, ...ledger } = page;
if (pagination.total !== obligations.length)
throw new TypeError("Incomplete obligation page set");
return { ...ledger, obligations };
};
const originalCases = (
obligation: ReconstructionObligationLedgerPage["obligations"][number],
obligation: ReconstructionObligationLedger["obligations"][number],
evidenceId: string,
) =>
obligation.required_case_kinds.map((caseKind) => ({
@@ -150,45 +139,26 @@ const reviewedObligation = (
});
describe("reconstruction obligation ledger", () => {
it("emits deterministic closure identity and paging", () => {
it("emits the complete deterministic ledger and closure identity", () => {
const evidence = proofEvidence("review");
const reviewed = [
reviewedObligation("obl.review.one", evidence.evidence_id),
reviewedObligation("obl.review.two", evidence.evidence_id),
];
const first = build(
request([evidence], {
reviewed_obligations: reviewed,
page: { offset: 0, limit: 1 },
}),
request([evidence], { reviewed_obligations: reviewed }),
);
const second = build(
request([evidence], {
reviewed_obligations: reviewed,
page: { offset: 1, limit: 1 },
}),
request([evidence], { reviewed_obligations: reviewed }),
);
expect(first.page).toEqual({
offset: 0,
limit: 1,
total: 2,
returned: 1,
next_offset: 1,
});
expect(second.page.next_offset).toBeNull();
expect(first.obligations).toHaveLength(2);
expect(first).toEqual(second);
expect(second.ledger_id).toBe(first.ledger_id);
expect(second.closure_digest).toBe(first.closure_digest);
expect(first.obligations[0]?.source_state).toBe("reviewed");
expect(first.status).toBe("open");
expect(
serializeReconstructionObligationLedger(
completeLedger(first, [...first.obligations, ...second.obligations]),
),
).toBe(
serializeReconstructionObligationLedger(
completeLedger(second, [...first.obligations, ...second.obligations]),
),
expect(serializeReconstructionObligationLedger(first)).toBe(
serializeReconstructionObligationLedger(second),
);
});
@@ -9,9 +9,9 @@ import { createEvidence, type Evidence } from "../domain/evidence.js";
import { jsonObjectSchema, jsonValueSchema } from "../domain/jsonValue.js";
import {
reconstructionObligationLedgerInputSchema,
reconstructionObligationLedgerPageSchema,
reconstructionObligationLedgerSchema,
type ReconstructionObligationLedgerInput,
type ReconstructionObligationLedgerPage,
type ReconstructionObligationLedger,
} from "../domain/reconstructionObligationLedgerSchemas.js";
import { err, ok, type Result } from "../domain/result.js";
import { JAVASCRIPT_APPLICATION_WORKFLOW_PROVIDER } from "./InvestigationProviders.js";
@@ -30,7 +30,7 @@ export const resolveReconstructionObligationLedgerRequest = (
: err(new AnalysisInputError(OPERATION, { cause: parsed.error }));
};
/** Build one deterministic ledger page and wrap it in portable Evidence. */
/** Build the complete deterministic ledger and wrap it in portable Evidence. */
export const buildReconstructionObligationLedgerEvidenceValidated = (
input: ReconstructionObligationLedgerInput,
): Result<Evidence, AnalysisError> => {
@@ -45,8 +45,7 @@ export const buildReconstructionObligationLedgerEvidenceValidated = (
manifest: input.manifest,
generationLimitations: generated.limitations,
});
const page = pageLedger(ledger, input.page.offset, input.page.limit);
return ok(createLedgerEvidence(input, page));
return ok(createLedgerEvidence(input, ledger));
} catch (cause: unknown) {
if (cause instanceof z.ZodError)
return err(new AnalysisInputError(OPERATION, { cause }));
@@ -59,47 +58,24 @@ export const buildReconstructionObligationLedgerEvidenceValidated = (
}
};
const pageLedger = (
ledger: ReturnType<typeof evaluateReconstructionObligationLedger>,
offset: number,
limit: number,
): ReconstructionObligationLedgerPage => {
const obligations = ledger.obligations.slice(offset, offset + limit);
const nextOffset =
offset + obligations.length < ledger.obligations.length
? offset + obligations.length
: null;
const { obligations: _allObligations, ...summary } = ledger;
return reconstructionObligationLedgerPageSchema.parse({
...summary,
page: {
offset,
limit,
total: ledger.obligations.length,
returned: obligations.length,
next_offset: nextOffset,
},
obligations,
});
};
const createLedgerEvidence = (
input: ReconstructionObligationLedgerInput,
page: ReconstructionObligationLedgerPage,
): Evidence =>
createEvidence(undefined, JAVASCRIPT_APPLICATION_WORKFLOW_PROVIDER, {
ledger: ReconstructionObligationLedger,
): Evidence => {
const completeLedger = reconstructionObligationLedgerSchema.parse(ledger);
return createEvidence(undefined, JAVASCRIPT_APPLICATION_WORKFLOW_PROVIDER, {
predicateType: "rea.reconstruction-obligation-ledger/v1",
operation: OPERATION,
parameters: jsonObjectSchema.parse({
ledger_id: page.ledger_id,
closure_digest: page.closure_digest,
page: input.page,
ledger_id: completeLedger.ledger_id,
closure_digest: completeLedger.closure_digest,
}),
result: jsonValueSchema.parse(page),
result: jsonValueSchema.parse(completeLedger),
rawResult: null,
confidence: "inferred",
authority: "analyst-inference",
environment: null,
limitations: page.limitations,
evidenceLinks: page.evidence_links,
limitations: completeLedger.limitations,
evidenceLinks: completeLedger.evidence_links,
});
};
+2 -2
View File
@@ -125,8 +125,8 @@ const buildPermissionCeilings = (
const ceilings: PermissionCeiling[] = [
permissionScope("evidence_read", arrays.evidenceRoots),
permissionScope("evidence_write", arrays.evidenceRoots),
permissionScope("investigation_workspace_read", arrays.evidenceRoots),
permissionScope("investigation_workspace_write", arrays.evidenceRoots),
permissionScope("reconstruction_coverage_read", arrays.evidenceRoots),
permissionScope("reconstruction_coverage_write", arrays.evidenceRoots),
permissionScope("snapshot_read", arrays.analysisSnapshotRoots),
permissionScope("snapshot_write", arrays.analysisSnapshotRoots),
permissionScope("artifact_extract", ["/"]),
+4 -5
View File
@@ -28,7 +28,7 @@ import {
import { reconstructionClosureResultSchema } from "../domain/reconstructionCoverage.js";
import {
reconstructionObligationLedgerInputSchema,
reconstructionObligationLedgerPageSchema,
reconstructionObligationLedgerSchema,
} from "../domain/reconstructionObligationLedgerSchemas.js";
import {
reconstructionReadinessInputSchema,
@@ -60,7 +60,7 @@ const exportShapeComparisonOutputSchema = evidenceResultOf(
javaScriptExportShapeComparisonResultSchema,
);
const reconstructionObligationLedgerOutputSchema = evidenceResultOf(
reconstructionObligationLedgerPageSchema,
reconstructionObligationLedgerSchema,
);
const reconstructionReadinessOutputSchema = evidenceResultOf(
reconstructionReadinessReportSchema,
@@ -301,13 +301,13 @@ export const APPLICATION_TOOL_CONTRACTS = [
name: "build_reconstruction_obligation_ledger",
...toolContractMetadata("build_reconstruction_obligation_ledger"),
description:
"Generate one deterministic page of a ReconstructionObligationLedger from an authenticated Evidence bundle, reviewed obligations, and an explicit reconstruction manifest. Static candidates remain candidates; duplicate ownership, missing original or reconstruction cases, missing parser/type, weak verifier authority, unenumerated claims, contradictions, dependencies, and residual unknowns fail closed. Every page carries the same full-ledger closure digest and typed per-obligation diagnostics.",
"Generate the complete deterministic ReconstructionObligationLedger from an authenticated Evidence bundle, reviewed obligations, and an explicit reconstruction manifest. Static candidates remain candidates; duplicate ownership, missing original or reconstruction cases, missing parser/type, weak verifier authority, unenumerated claims, contradictions, dependencies, and residual unknowns fail closed. The inline result carries the full-ledger closure digest and typed per-obligation diagnostics.",
kind: "application",
inputSchema: reconstructionObligationLedgerInputSchema,
outputSchema: reconstructionObligationLedgerOutputSchema,
examples: [
{
title: "Generate the first obligation-ledger page",
title: "Generate the complete obligation ledger",
input: {
evidence_bundle: {
artifacts: [],
@@ -323,7 +323,6 @@ export const APPLICATION_TOOL_CONTRACTS = [
bindings: [],
contradictions: [],
},
page: { offset: 0, limit: 50 },
},
},
],
@@ -108,7 +108,6 @@ const compareJavaScriptExportShapesFacts = {
compareJavaScriptExportShapesInputSchema.shape.right_module_path,
right_export_name:
compareJavaScriptExportShapesInputSchema.shape.right_export_name,
limits: compareJavaScriptExportShapesInputSchema.shape.limits,
unknown_registry_approved:
compareJavaScriptExportShapesInputSchema.shape.unknown_registry_approved,
} as const;
+2 -2
View File
@@ -54,8 +54,8 @@ export const TOOL_EXAMPLE_OVERRIDES: Readonly<
right_evidence_id: `ev_${"1".repeat(64)}`,
},
compare_artifacts: {
left_evidence_ids: [ARTIFACT_COMPARISON_EXAMPLE.left.evidence_id],
right_evidence_ids: [ARTIFACT_COMPARISON_EXAMPLE.right.evidence_id],
left_evidence_id: ARTIFACT_COMPARISON_EXAMPLE.left.evidence_id,
right_evidence_id: ARTIFACT_COMPARISON_EXAMPLE.right.evidence_id,
},
compare_functions: {
left_evidence_id: FUNCTION_COMPARISON_EXAMPLE.left.evidence_id,
@@ -70,8 +70,6 @@ export const targetKindSchema = z.enum([
const providerCapabilityFacts = {
operation: z.string(),
pagination: z.enum(["none", "offset", "cursor"]),
exhaustive: z.boolean(),
effects: z.object({
mutates_artifact: z.boolean(),
launches_process: z.boolean(),
@@ -81,11 +79,6 @@ const providerCapabilityFacts = {
changes_permissions: z.boolean(),
requires_root: z.boolean(),
}),
limits: z.object({
max_results: z.number().int().min(0).nullable(),
max_payload_bytes: z.number().int().min(0).nullable(),
timeout_ms: z.number().int().min(0).nullable(),
}),
limitations: z.array(z.string()),
};
@@ -418,26 +411,6 @@ const availableMemoryPermissions = z.object({
source: z.literal("ghidra-memory-block"),
});
export const bounded = (item: z.ZodType) => {
const facts = {
items: z.array(item),
total: z.number().int().min(0).nullable(),
returned: z.number().int().min(0),
} as const;
return z.discriminatedUnion("truncated", [
z.object({
...facts,
truncated: z.literal(false),
next_offset: z.null(),
}),
z.object({
...facts,
truncated: z.literal(true),
next_offset: z.number().int().min(0).nullable(),
}),
]);
};
export const addressedValue = z.object({
address: z.string(),
value: z.string(),
-6
View File
@@ -29,11 +29,9 @@ const PROPERTY_DESCRIPTIONS: Readonly<Record<string, string>> = {
question: "Concrete unresolved question to retain for later investigation.",
result: "Primary structured result returned by this operation.",
left: "Left-hand input used for comparison or differential execution.",
limit: "Maximum number of results to return in this page.",
limits:
"Bounded resource-consumption and result-size limits for this operation.",
mode: "Operation mode that selects the requested behavior.",
offset: "Zero-based index of the first result to return.",
overwrite: "Whether an existing destination may be replaced.",
path: "Local filesystem path used by this operation.",
provider_id:
@@ -119,10 +117,6 @@ const fallbackPropertyDescription = (property: string): string => {
const words = property.replaceAll("_", " ");
if (property.endsWith("_approved"))
return `Explicit operator approval for ${words.slice(0, -9)}.`;
if (property.endsWith("_offset"))
return `Zero-based index of the first ${words.slice(0, -7)} to return.`;
if (property.endsWith("_limit"))
return `Maximum number of ${words.slice(0, -6)} entries to return.`;
if (property.startsWith("max_"))
return `Maximum permitted ${words.slice(4)} for this operation.`;
if (property.startsWith("include_"))
+10 -10
View File
@@ -11,7 +11,7 @@ import {
HopperProcessError,
HopperStartError,
HopperTimeoutError,
InvestigationWorkspaceError,
WorkspaceStorageError,
NoBinaryOpenError,
PermissionRequiredError,
ProviderSelectionError,
@@ -81,8 +81,8 @@ export const analysisErrorCategory = (
return artifactErrorCategory(error.reason);
if (error instanceof EvidenceFileError && error.reason === "disabled")
return "unavailable";
if (error instanceof InvestigationWorkspaceError)
return investigationWorkspaceCategory(error.reason);
if (error instanceof WorkspaceStorageError)
return workspaceStorageCategory(error.reason);
return STATIC_ERROR_CATEGORIES[error._tag] ?? "execution_failure";
};
@@ -101,8 +101,8 @@ const browserErrorCategory = (
return "execution_failure";
};
const investigationWorkspaceCategory = (
reason: InvestigationWorkspaceError["reason"],
const workspaceStorageCategory = (
reason: WorkspaceStorageError["reason"],
): AnalysisErrorProjection["category"] => {
if (
reason === "disabled" ||
@@ -162,8 +162,8 @@ export const analysisErrorUserMessage = (error: AnalysisError): string => {
return "Evidence is too large for this session. Reduce the evidence set and try again.";
if (error instanceof EvidenceFileError)
return evidenceFileMessage(error.reason);
if (error instanceof InvestigationWorkspaceError)
return investigationWorkspaceMessage(error.reason);
if (error instanceof WorkspaceStorageError)
return workspaceStorageMessage(error.reason);
if (error instanceof UnknownRegistryError && error.reason === "not-found")
return "The requested residual unknown does not exist in this session. Check the unknown_id and try again.";
if (error instanceof UnknownRegistryError)
@@ -274,8 +274,8 @@ const evidenceFileMessage = (reason: EvidenceFileError["reason"]): string => {
return "Evidence file could not be accessed. Check file permissions and try again.";
};
const investigationWorkspaceMessage = (
reason: InvestigationWorkspaceError["reason"],
const workspaceStorageMessage = (
reason: WorkspaceStorageError["reason"],
): string => {
if (reason === "disabled")
return "Investigation workspace access is disabled. Configure a workspace directory and try again.";
@@ -309,7 +309,7 @@ const KNOWN_ERROR_TAGS = {
EvidenceIntegrityError: true,
EvidenceLimitError: true,
EvidenceFileError: true,
InvestigationWorkspaceError: true,
WorkspaceStorageError: true,
UnknownRegistryError: true,
HopperTimeoutError: true,
HopperCancelledError: true,
@@ -246,32 +246,6 @@ describe("analysis error projection: caller contract", () => {
});
});
it("retains schema-authored input guidance", () => {
expect(
projectAnalysisError(
new AnalysisInputError("find_changed_behavior", undefined, [
{
path: [],
reason: "invalid_value",
message:
"Supply either existing comparisons or one investigation_run",
},
]),
),
).toMatchObject({
details: {
issues: [
{
path: [],
reason: "invalid_value",
message:
"Supply either existing comparisons or one investigation_run",
},
],
},
});
});
it("distinguishes disabled integrity continuation from format support", () => {
expect(
projectAnalysisError(
+8 -8
View File
@@ -18,7 +18,7 @@ import {
HopperProcessError,
HopperRemoteError,
HopperTimeoutError,
InvestigationWorkspaceError,
WorkspaceStorageError,
PermissionRequiredError,
ProviderAdapterError,
ProviderSelectionError,
@@ -73,8 +73,8 @@ const errorCode = (error: AnalysisError): AnalysisErrorProjection["code"] => {
if (error instanceof ArtifactOperationError)
return artifactOperationCode(error);
if (error instanceof EvidenceFileError) return evidenceFileCode(error.reason);
if (error instanceof InvestigationWorkspaceError)
return investigationWorkspaceCode(error.reason);
if (error instanceof WorkspaceStorageError)
return workspaceStorageCode(error.reason);
if (error instanceof UnknownRegistryError)
return unknownRegistryCode(error.reason);
if (error._tag === "ProcessCaptureError") return processCaptureCode(error);
@@ -115,8 +115,8 @@ const evidenceFileCode = (
return "execution_failure";
};
const investigationWorkspaceCode = (
reason: InvestigationWorkspaceError["reason"],
const workspaceStorageCode = (
reason: WorkspaceStorageError["reason"],
): AnalysisErrorProjection["code"] => {
if (reason === "revision-conflict" || reason === "name-conflict")
return "revision_conflict";
@@ -152,7 +152,7 @@ type SpecializedErrorTag =
| "ArtifactOperationError"
| "BrowserObservationError"
| "EvidenceFileError"
| "InvestigationWorkspaceError"
| "WorkspaceStorageError"
| "PermissionRequiredError"
| "ProcessCaptureError"
| "ProviderSelectionError"
@@ -192,7 +192,7 @@ const staticErrorCode = (
case "ArtifactOperationError":
case "BrowserObservationError":
case "EvidenceFileError":
case "InvestigationWorkspaceError":
case "WorkspaceStorageError":
case "PermissionRequiredError":
case "ProcessCaptureError":
case "ProviderSelectionError":
@@ -268,7 +268,7 @@ const artifactStateErrorDetails = (
};
if (error instanceof EvidenceLimitError)
return { limit: error.limit, maximum: error.maximum, truncated: true };
if (error instanceof InvestigationWorkspaceError)
if (error instanceof WorkspaceStorageError)
return { operation: error.operation, reason: error.reason };
if (error instanceof UnknownRegistryError) return { reason: error.reason };
if (error instanceof EvidenceFileError)
+2 -4
View File
@@ -65,9 +65,7 @@ describe("analysis snapshot contract", () => {
).toBeUndefined();
});
it("rejects snapshot v1 with explicit recapture guidance", () => {
expect(() => parseAnalysisSnapshot({ snapshot_version: 1 })).toThrow(
/v1.*recapture.*v2/iu,
);
it("rejects an input without the current snapshot shape", () => {
expect(() => parseAnalysisSnapshot({ snapshot_version: 1 })).toThrow();
});
});
+1 -14
View File
@@ -270,9 +270,8 @@ export const createAnalysisSnapshotEntry = (input: {
};
};
/** Parse v2 and reject legacy snapshots, altered query IDs, or non-canonical order. */
/** Parse a provider/profile-exact snapshot and verify its entries and evidence. */
export const parseAnalysisSnapshot = (input: unknown): AnalysisSnapshot => {
rejectLegacySnapshot(input);
const parsed = analysisSnapshotSchema.parse(input);
parseEvidenceBundle(parsed.evidence_bundle);
if (
@@ -314,15 +313,3 @@ export const parseAnalysisSnapshot = (input: unknown): AnalysisSnapshot => {
/** Serialize a validated snapshot with byte-stable canonical entry ordering. */
export const serializeAnalysisSnapshot = (snapshot: AnalysisSnapshot): string =>
`${canonicalJson(parseAnalysisSnapshot(snapshot))}\n`;
const rejectLegacySnapshot = (input: unknown): void => {
if (
typeof input === "object" &&
input !== null &&
"snapshot_version" in input &&
input.snapshot_version === 1
)
throw new TypeError(
"Analysis snapshot v1 is incompatible with profile-exact replay; recapture it as snapshot v2. Its Evidence bundle may be imported separately.",
);
};
+53 -61
View File
@@ -29,7 +29,7 @@ describe("bundle comparison", () => {
const second = evidence("second");
const left = createEvidenceBundle([first, second]);
const right = createEvidenceBundle([second, first]);
const result = compareBundles(left, right, [], 0, 100);
const result = compareBundles(left, right);
expect(bundleComparisonResultSchema.parse(result)).toMatchObject({
status: "unchanged",
summary: {
@@ -38,12 +38,12 @@ describe("bundle comparison", () => {
records_removed: 0,
unresolved: 0,
},
changes: { total: 0, next_offset: null },
changes: [],
});
expect(result.left_bundle_sha256).toBe(result.right_bundle_sha256);
});
it("classifies explicit pairs and pages stable membership changes", () => {
it("classifies explicit pairs and returns every membership change inline", () => {
const oldRecord = evidence("old");
const newRecord = evidence("new");
const removed = evidence("removed");
@@ -56,8 +56,8 @@ describe("bundle comparison", () => {
right_evidence_id: newRecord.evidence_id,
},
];
const first = compareBundles(left, right, pairs, 0, 1);
const repeated = compareBundles(left, right, pairs, 0, 1);
const first = compareBundles(left, right, pairs);
const repeated = compareBundles(left, right, pairs);
expect(first).toEqual(repeated);
expect(first).toMatchObject({
status: "changed",
@@ -66,61 +66,57 @@ describe("bundle comparison", () => {
records_removed: 1,
records_changed: 1,
},
changes: { total: 3, next_offset: 1 },
changes: expect.arrayContaining([
expect.objectContaining({ classification: "changed" }),
]),
});
expect(
compareBundles(left, right, pairs, 1, 500).changes.items,
).toHaveLength(2);
expect(first.changes).toHaveLength(3);
expect(first.limitations).toContain(
"One-sided membership proves only bundle inclusion or omission, not behavioral absence.",
);
});
it("returns more than 500 changes without a display-page ceiling", () => {
const records = Array.from({ length: 501 }, (_, index) =>
evidence(`record-${String(index)}`),
);
const result = compareBundles(
createEvidenceBundle([]),
createEvidenceBundle(records),
);
expect(result.changes).toHaveLength(501);
expect(bundleComparisonResultSchema.parse(result).changes).toHaveLength(
501,
);
});
it("rejects missing and non-bijective explicit pairs", () => {
const leftRecord = evidence("left");
const rightRecord = evidence("right");
const left = createEvidenceBundle([leftRecord]);
const right = createEvidenceBundle([rightRecord]);
expect(() =>
compareBundles(
left,
right,
[
{
left_evidence_id: evidence("missing").evidence_id,
right_evidence_id: rightRecord.evidence_id,
},
],
0,
100,
),
compareBundles(left, right, [
{
left_evidence_id: evidence("missing").evidence_id,
right_evidence_id: rightRecord.evidence_id,
},
]),
).toThrow(/missing left evidence/u);
expect(() =>
compareBundles(
left,
right,
[
{
left_evidence_id: leftRecord.evidence_id,
right_evidence_id: rightRecord.evidence_id,
},
{
left_evidence_id: leftRecord.evidence_id,
right_evidence_id: rightRecord.evidence_id,
},
],
0,
100,
),
compareBundles(left, right, [
{
left_evidence_id: leftRecord.evidence_id,
right_evidence_id: rightRecord.evidence_id,
},
{
left_evidence_id: leftRecord.evidence_id,
right_evidence_id: rightRecord.evidence_id,
},
]),
).toThrow(/one-to-one/u);
expect(() =>
compareBundles(
{ ...left, records: [leftRecord, leftRecord] },
right,
[],
0,
100,
),
compareBundles({ ...left, records: [leftRecord, leftRecord] }, right),
).toThrow(/duplicate record IDs/u);
});
});
@@ -170,31 +166,27 @@ describe("bundle comparison history", () => {
[mutationOne, mutationTwo],
[initial, advanced],
);
expect(
compareBundles(initialBundle, advancedBundle, [], 0, 100),
).toMatchObject({
expect(compareBundles(initialBundle, advancedBundle)).toMatchObject({
status: "changed",
summary: { unknowns_advanced: 1, unresolved: 0 },
changes: {
items: [
expect.objectContaining({
entity: "evidence",
classification: "added",
}),
expect.objectContaining({
entity: "residual_unknown",
classification: "history_advanced",
}),
],
},
changes: [
expect.objectContaining({
entity: "evidence",
classification: "added",
}),
expect.objectContaining({
entity: "residual_unknown",
classification: "history_advanced",
}),
],
});
const absent = createEvidenceBundle([]);
const missing = compareBundles(initialBundle, absent, [], 0, 100);
const missing = compareBundles(initialBundle, absent);
expect(missing).toMatchObject({
status: "unknown",
summary: { unknowns_removed: 1, unresolved: 1 },
});
expect(missing.changes.items).toContainEqual(
expect(missing.changes).toContainEqual(
expect.objectContaining({
classification: "removed",
conclusion_kind: "unresolved_branch",
+6 -27
View File
@@ -20,7 +20,7 @@ const classificationSchema = z.enum([
"history_diverged",
]);
/** Strict bounded input for canonical Evidence bundle comparison. */
/** Strict input for canonical Evidence bundle comparison. */
export const bundleComparisonInputSchema = z.strictObject({
left_bundle_path: z.string().min(1).max(4_096),
right_bundle_path: z.string().min(1).max(4_096),
@@ -31,10 +31,7 @@ export const bundleComparisonInputSchema = z.strictObject({
right_evidence_id: evidenceIdSchema,
}),
)
.max(500)
.default([]),
offset: z.number().int().min(0).default(0),
limit: z.number().int().min(1).max(500).default(100),
});
const bundleChangeSchema = z.object({
@@ -70,13 +67,7 @@ export const bundleComparisonResultSchema = z.object({
unknowns_diverged: z.number().int().min(0),
unresolved: z.number().int().min(0),
}),
changes: z.object({
items: z.array(bundleChangeSchema).max(500),
offset: z.number().int().min(0),
limit: z.number().int().min(1).max(500),
total: z.number().int().min(0),
next_offset: z.number().int().min(0).nullable(),
}),
changes: z.array(bundleChangeSchema),
limitations: z.array(z.string()),
});
@@ -92,13 +83,9 @@ const MAX_ENTITIES = 100_000;
/** Compare canonical bundle membership and only explicitly paired observations. */
export const compareBundles = (
...[leftInput, rightInput, recordPairs, offset, limit]: readonly [
unknown,
unknown,
readonly RecordPair[],
number,
number,
]
leftInput: unknown,
rightInput: unknown,
recordPairs: readonly RecordPair[] = [],
): BundleComparisonResult => {
const left = parseEvidenceBundle(leftInput);
const right = parseEvidenceBundle(rightInput);
@@ -110,7 +97,6 @@ export const compareBundles = (
const changes = [...recordResult.changes, ...unknownResult.changes].sort(
compareChanges,
);
const page = changes.slice(offset, offset + limit);
const unresolved = changes.filter(
({ conclusion_kind: kind }) => kind !== "observed_change",
).length;
@@ -128,14 +114,7 @@ export const compareBundles = (
...unknownResult.summary,
unresolved,
},
changes: {
items: page,
offset,
limit,
total: changes.length,
next_offset:
offset + page.length < changes.length ? offset + page.length : null,
},
changes,
limitations: changes.some(({ limitations }) => limitations.length > 0)
? [
"One-sided membership proves only bundle inclusion or omission, not behavioral absence.",
+34 -61
View File
@@ -85,11 +85,6 @@ const processResult = (
});
const artifactResult = (
pagination: {
readonly offset?: number;
readonly next_offset?: number | null;
readonly total?: number;
} = {},
nestedLinks: string[] = [left.evidence_id, right.evidence_id],
): JsonValue => ({
status: "changed",
@@ -102,24 +97,18 @@ const artifactResult = (
changed: 1,
unknown: 0,
},
changes: {
items: [
{
classification: "changed",
logical_path: "main.js",
dimensions: ["content"],
left_occurrence_id: `occ_${"5".repeat(64)}`,
right_occurrence_id: `occ_${"6".repeat(64)}`,
left_artifact_id: `art_${"7".repeat(64)}`,
right_artifact_id: `art_${"8".repeat(64)}`,
evidence_links: nestedLinks,
},
],
offset: pagination.offset ?? 0,
limit: 100,
total: pagination.total ?? 1,
next_offset: pagination.next_offset ?? null,
},
changes: [
{
classification: "changed",
logical_path: "main.js",
dimensions: ["content"],
left_occurrence_id: `occ_${"5".repeat(64)}`,
right_occurrence_id: `occ_${"6".repeat(64)}`,
left_artifact_id: `art_${"7".repeat(64)}`,
right_artifact_id: `art_${"8".repeat(64)}`,
evidence_links: nestedLinks,
},
],
limitations: [],
});
@@ -134,11 +123,9 @@ const expectInvalidSummary = (result: ChangedBehaviorResult): void => {
describe("changed behavior", () => {
it("accepts process comparison Evidence", () => {
const result = findChangedBehavior(
[comparison("compare_process_captures", processResult())],
0,
100,
);
const result = findChangedBehavior([
comparison("compare_process_captures", processResult()),
]);
expect(result.behavior_status).toBe("observed_unchanged");
});
@@ -152,7 +139,7 @@ describe("changed behavior", () => {
}),
);
expect(findChangedBehavior([evidence], 0, 100)).toMatchObject({
expect(findChangedBehavior([evidence])).toMatchObject({
behavior_status: "observed_changed",
summary: { observed_changes: 2 },
findings: {
@@ -172,11 +159,11 @@ describe("changed behavior", () => {
"compare_process_captures",
processResult({ status: "changed", terminal: "changed" }),
);
const result = findChangedBehavior([evidence], 0, 100);
const result = findChangedBehavior([evidence]);
expect(changedBehaviorResultSchema.parse(result)).toMatchObject({
behavior_status: "observed_changed",
summary: { observed_changes: 1, static_candidates: 0 },
findings: { total: 1, next_offset: null },
findings: { items: expect.any(Array) },
});
expectInvalidSummary(result);
expect(result.findings.items[0]).toMatchObject({
@@ -193,7 +180,7 @@ describe("changed behavior", () => {
it("keeps static differences as candidates, never runtime observations", () => {
const evidence = comparison("compare_artifacts", artifactResult());
const result = findChangedBehavior([evidence], 0, 100);
const result = findChangedBehavior([evidence]);
expect(result).toMatchObject({
behavior_status: "unknown",
summary: { observed_changes: 0, static_candidates: 1 },
@@ -217,40 +204,26 @@ describe("changed behavior", () => {
"compare_process_captures",
processResult({ status: "unknown", exit: "unknown" }),
);
const result = findChangedBehavior([changed, unknown], 0, 1);
const result = findChangedBehavior([changed, unknown]);
expect(result).toMatchObject({
behavior_status: "unknown",
findings: { total: 2, limit: 1, next_offset: 1 },
findings: { items: expect.any(Array) },
});
expect(findChangedBehavior([changed, unknown], 0, 1)).toEqual(result);
expect(findChangedBehavior([changed, unknown])).toEqual(result);
});
it("keeps incomplete artifact comparison pagination explicit", () => {
const paged = comparison(
"compare_artifacts",
artifactResult({ next_offset: 1, total: 2 }),
);
const result = findChangedBehavior([paged], 0, 100);
expect(result).toMatchObject({
behavior_status: "unknown",
summary: { static_candidates: 1, unresolved: 1 },
findings: { total: 2 },
});
expect(result.limitations).toContain(
"Artifact comparison reports 1 of 2 changes.",
);
const laterPage = comparison(
"compare_artifacts",
artifactResult({ offset: 1, next_offset: null, total: 2 }),
);
expect(() => findChangedBehavior([laterPage], 0, 100)).toThrow(
/pagination from offset zero/u,
it("uses complete inline artifact deltas as static candidates", () => {
const complete = comparison("compare_artifacts", artifactResult());
expect(findChangedBehavior([complete]).findings.items).toEqual(
expect.arrayContaining([
expect.objectContaining({ dimension: "artifact:main.js" }),
]),
);
});
it("rejects bundles, duplicate comparisons, and malformed exact results", () => {
const valid = comparison("compare_process_captures", processResult());
expect(() => findChangedBehavior([valid, valid], 0, 100)).toThrow(
expect(() => findChangedBehavior([valid, valid])).toThrow(
/duplicate comparison Evidence/u,
);
const bundle = createEvidence(
@@ -266,25 +239,25 @@ describe("changed behavior", () => {
evidenceLinks: [left.evidence_id, right.evidence_id],
},
);
expect(() => findChangedBehavior([bundle], 0, 100)).toThrow(
expect(() => findChangedBehavior([bundle])).toThrow(
/requires process, artifact, or function/u,
);
const malformed = comparison("compare_process_captures", {
status: "unchanged",
});
expect(() => findChangedBehavior([malformed], 0, 100)).toThrow();
expect(() => findChangedBehavior([malformed])).toThrow();
const contradictory = comparison(
"compare_process_captures",
processResult({ status: "unchanged", terminal: "changed" }),
);
expect(() => findChangedBehavior([contradictory], 0, 100)).toThrow(
expect(() => findChangedBehavior([contradictory])).toThrow(
/contradicts its dimensions/u,
);
const danglingNested = comparison(
"compare_artifacts",
artifactResult({}, [`ev_${"f".repeat(64)}`, right.evidence_id]),
artifactResult([`ev_${"f".repeat(64)}`, right.evidence_id]),
);
expect(() => findChangedBehavior([danglingNested], 0, 100)).toThrow(
expect(() => findChangedBehavior([danglingNested])).toThrow(
/outside its top-level closure/u,
);
});
+10 -88
View File
@@ -9,33 +9,14 @@ import {
PROCESS_COMPARISON_DIMENSIONS,
processCaptureComparisonSchema,
} from "./processCapture.js";
import {
crossVersionInvestigationInputSchema,
investigationRunSummarySchema,
} from "./investigationWorkspace.js";
const evidenceIdSchema = z.string().regex(/^ev_[a-f0-9]{64}$/u);
/** Strict bounded input for aggregating existing comparison Evidence. */
export const changedBehaviorInputSchema = z
.object({
comparisons: z.array(evidenceSchema).max(100).default([]),
investigation_run: crossVersionInvestigationInputSchema.optional(),
offset: z.number().int().min(0).default(0),
limit: z.number().int().min(1).max(100).default(100),
unknown_registry_approved: z.literal(true).optional(),
})
.superRefine((input, context) => {
if (
input.comparisons.length > 0 ===
(input.investigation_run !== undefined)
)
context.addIssue({
code: "custom",
message:
"Supply either existing comparisons or one investigation_run, but not both",
});
});
export const changedBehaviorInputSchema = z.object({
comparisons: z.array(evidenceSchema).max(100).default([]),
unknown_registry_approved: z.literal(true).optional(),
});
const findingSchema = z.object({
scope: z.enum(["runtime", "protocol", "resource", "static_candidate"]),
@@ -68,47 +49,22 @@ export const changedBehaviorResultSchema = z
unresolved: z.number().int().min(0),
}),
findings: z.object({
items: z.array(findingSchema).max(100),
offset: z.number().int().min(0),
limit: z.number().int().min(1).max(100),
total: z.number().int().min(0),
next_offset: z.number().int().min(0).nullable(),
items: z.array(findingSchema),
}),
evidence_links: z.array(evidenceIdSchema).min(3).max(20_100),
limitations: z.array(z.string()),
investigation_run: investigationRunSummarySchema.optional(),
})
.superRefine((result, context) => {
const summaryTotal = Object.values(result.summary).reduce(
(total, count) => total + count,
0,
);
const expectedNextOffset =
result.findings.offset + result.findings.items.length <
result.findings.total
? result.findings.offset + result.findings.items.length
: null;
if (summaryTotal !== result.findings.total)
if (summaryTotal !== result.findings.items.length)
context.addIssue({
code: "custom",
message: "Finding classification counts must equal the finding total",
path: ["summary"],
});
if (result.findings.next_offset !== expectedNextOffset)
context.addIssue({
code: "custom",
message: "Finding cursor must identify the next retained offset",
path: ["findings", "next_offset"],
});
if (
result.findings.offset < result.findings.total &&
result.findings.items.length === 0
)
context.addIssue({
code: "custom",
message: "A nonterminal finding page must retain at least one item",
path: ["findings", "items"],
});
if (
(result.behavior_status === "observed_changed" &&
result.summary.observed_changes === 0) ||
@@ -155,13 +111,9 @@ const acceptsPredicate = (
/** Find observed runtime changes and separately report static change candidates. */
export const findChangedBehavior = (
comparisonsInput: unknown,
offset: number,
limit: number,
): ChangedBehaviorResult => {
const parsedInput = changedBehaviorInputSchema.parse({
comparisons: comparisonsInput,
offset,
limit,
});
const comparisons = parsedInput.comparisons;
const evidence = comparisons.map(parseComparisonEvidence);
@@ -176,10 +128,6 @@ export const findChangedBehavior = (
);
if (links.length > 20_100)
throw new TypeError("Changed-behavior Evidence closure exceeds limit");
const page = findings.slice(
parsedInput.offset,
parsedInput.offset + parsedInput.limit,
);
return changedBehaviorResultSchema.parse({
behavior_status: behaviorStatus(runtimeStatuses),
summary: {
@@ -189,14 +137,7 @@ export const findChangedBehavior = (
unresolved: count(findings, "unresolved_branch"),
},
findings: {
items: page,
offset: parsedInput.offset,
limit: parsedInput.limit,
total: findings.length,
next_offset:
parsedInput.offset + page.length < findings.length
? parsedInput.offset + page.length
: null,
items: findings,
},
evidence_links: links,
limitations: uniqueSorted([
@@ -211,7 +152,6 @@ export const findChangedBehavior = (
...(runtimeStatuses.length === 0
? ["No process comparison Evidence was supplied."]
: []),
...evidence.flatMap(artifactPaginationLimitations),
]),
});
};
@@ -278,13 +218,9 @@ const parseResult = (evidence: Evidence): void => {
const result = artifactComparisonResultSchema.parse(
evidence.normalized_result,
);
if (result.changes.offset !== 0)
throw new TypeError(
"Changed-behavior analysis requires artifact comparison pagination from offset zero",
);
assertNestedLinks(
evidence,
result.changes.items.flatMap(({ evidence_links: links }) => links),
result.changes.flatMap(({ evidence_links: links }) => links),
);
};
@@ -366,7 +302,7 @@ const artifactFindings = (evidence: Evidence): Finding[] => {
const result = artifactComparisonResultSchema.parse(
evidence.normalized_result,
);
const changes = result.changes.items.map((change) =>
const changes = result.changes.map((change) =>
makeFinding({
evidence,
dimension: `artifact:${change.logical_path}`,
@@ -382,9 +318,7 @@ const artifactFindings = (evidence: Evidence): Finding[] => {
links: change.evidence_links,
}),
);
return (result.status === "unknown" ||
result.status === "truncated" ||
result.changes.next_offset !== null) &&
return (result.status === "unknown" || result.status === "truncated") &&
!changes.some(
({ classification }) => classification === "unresolved_branch",
)
@@ -402,18 +336,6 @@ const artifactFindings = (evidence: Evidence): Finding[] => {
: changes;
};
const artifactPaginationLimitations = (evidence: Evidence): string[] => {
if (evidence.operation !== "compare_artifacts") return [];
const result = artifactComparisonResultSchema.parse(
evidence.normalized_result,
);
return result.changes.next_offset === null
? []
: [
`Artifact comparison reports ${String(result.changes.items.length)} of ${String(result.changes.total)} changes.`,
];
};
const makeFinding = (input: {
readonly evidence: Evidence;
readonly dimension: string;
@@ -12,16 +12,13 @@ const digest = (digit: string): string => digit.repeat(64);
const evidenceId = (digit: string): string => `ev_${digest(digit)}`;
const report = (): CompletionVerifierReport => ({
schema_version: 1,
verifier: { id: "managed-conformance", version: "1" },
verifier_run: {
schema_version: 1,
run_id: "11111111-1111-4111-8111-111111111111",
verifier_pid: 100,
parent_pid: 1,
process_lineage: {
status: "verified",
schema_version: 1,
observed_at: "2026-07-22T10:00:00.000Z",
launcher_pid: 100,
launcher_parent_pid: 1,
@@ -41,7 +38,6 @@ const report = (): CompletionVerifierReport => ({
scenario: { id: "inspect-managed", version: 1 },
artifact_sha256s: [digest("1")],
provider: { id: "managed-static", version: "2" },
result_schema_version: 1,
status: "pass",
evidence_ids: [evidenceId("1")],
},
@@ -50,7 +46,6 @@ const report = (): CompletionVerifierReport => ({
scenario: { id: "external-oracle", version: 1 },
artifact_sha256s: [],
provider: { id: "managed-static", version: "2" },
result_schema_version: 1,
status: "unsupported",
evidence_ids: [evidenceId("2")],
},
@@ -86,7 +81,6 @@ describe("completion ledger generation", () => {
artifact_sha256s: [digest("1")],
scenario: { id: "inspect-managed", version: 1 },
provider: { id: "managed-static", version: "2" },
result_schema_version: 1,
});
expect(generated.manifest.claims[0]).not.toHaveProperty("status");
expect(generated.manifest.claims[0]).not.toHaveProperty("evidence_ids");
@@ -142,7 +136,6 @@ describe("completion verifier report validation", () => {
...input.verifier_run,
process_lineage: {
status: "verified",
schema_version: 1,
observed_at: "2026-07-22T10:00:00.000Z",
launcher_pid: 100,
launcher_parent_pid: 1,
@@ -177,12 +170,6 @@ describe("completion ledger commitment drift", () => {
firstClaim(input).provider.version = "3";
},
],
[
"result schema",
(input: CompletionVerifierReport) => {
firstClaim(input).result_schema_version = 2;
},
],
[
"environment",
(input: CompletionVerifierReport) => {
-6
View File
@@ -31,14 +31,12 @@ const skillDigestSchema = z.strictObject({
});
const verifierRunSchema = z
.strictObject({
schema_version: z.literal(1),
run_id: z.string().uuid(),
verifier_pid: z.number().int().positive(),
parent_pid: z.number().int().nonnegative(),
process_lineage: z.discriminatedUnion("status", [
z.strictObject({
status: z.literal("verified"),
schema_version: z.literal(1),
observed_at: z.iso.datetime(),
launcher_pid: z.number().int().positive(),
launcher_parent_pid: z.number().int().nonnegative(),
@@ -80,13 +78,11 @@ const reportClaimSchema = z.strictObject({
scenario: scenarioSchema,
artifact_sha256s: z.array(digestSchema).max(100),
provider: identitySchema,
result_schema_version: z.number().int().positive(),
status: evidenceCompletionRecordSchema.shape.status,
evidence_ids: evidenceCompletionRecordSchema.shape.evidence_ids,
});
const completionVerifierReportObjectSchema = z.strictObject({
schema_version: z.literal(1),
verifier: identitySchema,
verifier_run: verifierRunSchema,
environment: environmentSchema,
@@ -114,7 +110,6 @@ const manifestClaimSchema = reportClaimSchema.omit({
evidence_ids: true,
});
const completionManifestObjectSchema = z.strictObject({
schema_version: z.literal(1),
manifest_id: z.string().regex(/^ecm_[a-f0-9]{64}$/u),
verifier: identitySchema,
environment: environmentSchema,
@@ -222,7 +217,6 @@ export const createCompletionLedgerArtifacts = (
if (!isSortedUnique(skills.map(({ skill_id }) => skill_id)))
throw new TypeError("Completion skill IDs must be unique");
const projection = {
schema_version: 1 as const,
verifier: report.verifier,
environment: report.environment,
skill_digests: skills,
-5
View File
@@ -7,9 +7,6 @@ import { evidenceEnvelopeSchema } from "./evidence.js";
import { evidenceBundleSchema } from "./evidenceBundle.js";
import { err, ok, type Result } from "./result.js";
/** Schema version for the conformance package format. */
export const CONFORMANCE_PACKAGE_VERSION = 1;
const conformancePackageIdSchema = z.string().regex(/^cp_[a-f0-9]{64}$/u);
const scenarioIdSchema = z.string().regex(/^[A-Za-z][A-Za-z0-9._-]{0,99}$/u);
@@ -87,7 +84,6 @@ const verifierContractSchema = z.strictObject({
});
const conformancePackageContentsSchema = z.strictObject({
schema_version: z.literal(CONFORMANCE_PACKAGE_VERSION),
name: z.string().min(1),
description: z.string().min(1),
created_at: z.string().datetime(),
@@ -311,7 +307,6 @@ const parseScenarioSection = (
const packageContents = (
pkg: ConformancePackageRecord,
): ConformancePackageContents => ({
schema_version: pkg.schema_version,
name: pkg.name,
description: pkg.description,
created_at: pkg.created_at,
+1 -3
View File
@@ -33,7 +33,6 @@ const completionSummarySchema = z.strictObject({
});
const completionLedgerObjectSchema = z.strictObject({
schema_version: z.literal(1),
ledger_id: z.string().regex(/^ecl_[a-f0-9]{64}$/u),
records: z.array(evidenceCompletionRecordSchema).min(1).max(10_000),
summary: completionSummarySchema,
@@ -95,7 +94,7 @@ const computeLedgerId = (
records: readonly EvidenceCompletionRecord[],
): string =>
`ecl_${createHash("sha256")
.update(canonicalJson({ schema_version: 1, records }))
.update(canonicalJson({ records }))
.digest("hex")}`;
const ledgerIssues = (
@@ -156,7 +155,6 @@ export const createEvidenceCompletionLedger = (
if (!isSortedUnique(record.evidence_ids))
throw new TypeError("Completion Evidence IDs must be unique per claim");
return evidenceCompletionLedgerSchema.parse({
schema_version: 1,
ledger_id: computeLedgerId(records),
records,
summary: summarize(records),
-1
View File
@@ -64,7 +64,6 @@ const failures = [
export type HopperStartupFailureCode = (typeof failures)[number][1];
interface HopperStartupDiagnosticContext {
readonly schema_version: 1;
readonly component: "hopper_private_display";
readonly operation: "probe" | "launch";
readonly reason: string;
+1 -10
View File
@@ -1,5 +1,3 @@
import type { JsonValue } from "./jsonValue.js";
export const ghidraFunctionClassification = () => ({
external: false,
thunk: false,
@@ -40,14 +38,6 @@ export const ghidraReferenceEdge = () => ({
kind: ghidraReferenceKind(),
});
export const ghidraBounded = (items: readonly JsonValue[]) => ({
items: [...items],
total: items.length,
returned: items.length,
truncated: false,
next_offset: null,
});
export const ghidraNativeApiBoundary = (mappingsTruncated = false) => ({
available: true as const,
provenance: "ghidra-high-function",
@@ -173,3 +163,4 @@ export const ghidraFunctionDossier = (
],
};
};
import type { JsonValue } from "./jsonValue.js";
@@ -16,7 +16,6 @@ export const javascriptExportInstrumentationInputSchema = z.strictObject({
});
export const javascriptExportTransformationManifestSchema = z.strictObject({
schema_version: z.literal(1),
kind: z.literal("commonjs-factory-export-v1"),
artifact_path: z.string().min(1).max(4_096),
original_sha256: digestSchema,
@@ -77,7 +76,6 @@ export const instrumentJavaScriptExport = (
const bindingStart = prefix.byteLength + source.byteLength;
const suffixStart = bindingStart + binding.byteLength;
const manifest = javascriptExportTransformationManifestSchema.parse({
schema_version: 1,
kind: "commonjs-factory-export-v1",
artifact_path: input.artifact_path,
original_sha256: input.artifact_sha256,
+6 -20
View File
@@ -2,7 +2,6 @@ import { compareCodePoints } from "./javascriptApplicationGraph.js";
import { digestExportShapeValue } from "./javascriptExportShapeComparisonIdentity.js";
import {
javaScriptExportShapeComparisonResultSchema,
type CompareJavaScriptExportShapesInput,
type JavaScriptExportShapeComparisonChange,
type JavaScriptExportShapeComparisonResult,
} from "./javascriptExportShapeComparisonSchemas.js";
@@ -25,29 +24,16 @@ import {
export interface JavaScriptExportShapeComparisonProjectionInput {
readonly left: JavaScriptExportShapeSideInput;
readonly right: JavaScriptExportShapeSideInput;
readonly limits: CompareJavaScriptExportShapesInput["limits"];
}
/** Compare exact selected export return shapes without executing JavaScript. */
export const compareJavaScriptExportShapes = (
input: JavaScriptExportShapeComparisonProjectionInput,
): JavaScriptExportShapeComparisonResult => {
const left = selectJavaScriptExport(
input.left,
input.limits.max_candidate_exports,
);
const right = selectJavaScriptExport(
input.right,
input.limits.max_candidate_exports,
);
const leftRetained = retainJavaScriptExportShapes(
left,
input.limits.max_return_variants,
);
const rightRetained = retainJavaScriptExportShapes(
right,
input.limits.max_return_variants,
);
const left = selectJavaScriptExport(input.left);
const right = selectJavaScriptExport(input.right);
const leftRetained = retainJavaScriptExportShapes(left);
const rightRetained = retainJavaScriptExportShapes(right);
const pairing = pairJavaScriptExportShapeVariants(
leftRetained.shapes,
rightRetained.shapes,
@@ -64,8 +50,8 @@ export const compareJavaScriptExportShapes = (
rightShapes: rightRetained.shapes,
evidenceLinks,
}).sort(compareJavaScriptExportShapeChanges);
const changes = allChanges.slice(0, input.limits.max_changes);
const omittedChanges = allChanges.length - changes.length;
const changes = allChanges;
const omittedChanges = 0;
const coverage = comparisonCoverage({
input,
left,
@@ -54,13 +54,7 @@ const sourceRangeSchema = z
});
});
const comparisonLimitsSchema = z.strictObject({
max_candidate_exports: z.number().int().min(1).max(1_000).default(100),
max_return_variants: z.number().int().min(1).max(1_000).default(128),
max_changes: z.number().int().min(1).max(10_000).default(1_000),
});
/** Two authenticated application graphs, exact export selectors, and bounds. */
/** Two authenticated application graphs and exact export selectors. */
export const compareJavaScriptExportShapesInputSchema = z
.strictObject({
left: evidenceSchema,
@@ -69,11 +63,6 @@ export const compareJavaScriptExportShapesInputSchema = z
left_export_name: selectorTextSchema,
right_module_path: selectorTextSchema,
right_export_name: selectorTextSchema,
limits: comparisonLimitsSchema.default({
max_candidate_exports: 100,
max_return_variants: 128,
max_changes: 1_000,
}),
unknown_registry_approved: z.literal(true).optional(),
})
.superRefine((input, context) => {
@@ -304,9 +293,6 @@ export const javaScriptExportShapeComparisonResultSchema = z.strictObject({
}),
});
export type CompareJavaScriptExportShapesInput = z.output<
typeof compareJavaScriptExportShapesInputSchema
>;
export type ProjectedExportReturnShapes = z.output<
typeof projectedExportReturnShapesSchema
>;
+6 -11
View File
@@ -53,7 +53,7 @@ export type SelectedJavaScriptExport =
readonly limitations: readonly string[];
};
/** Caller-retained return shapes and their exact caller-limit omission count. */
/** Complete return-shape inventory retained from an authenticated projection. */
export interface RetainedJavaScriptExportShapes {
readonly shapes: readonly Shape[];
readonly omitted: number;
@@ -62,7 +62,6 @@ export interface RetainedJavaScriptExportShapes {
/** Select one exact trusted export and its exact static return-shape observation. */
export const selectJavaScriptExport = (
side: JavaScriptExportShapeSideInput,
maxCandidates: number,
): SelectedJavaScriptExport => {
const candidates = exportCandidates(side.graph);
const exact = candidates.filter(
@@ -79,8 +78,7 @@ export const selectJavaScriptExport = (
: matchingOneSelector.length > 0
? matchingOneSelector
: candidates;
const retainedCandidates = diagnosticPool.slice(0, maxCandidates);
const base = selectorBase(side, diagnosticPool, retainedCandidates);
const base = selectorBase(side, diagnosticPool);
if (exact.length === 0)
return unresolvedSelection(
base,
@@ -128,33 +126,30 @@ export const selectJavaScriptExport = (
};
};
/** Apply the caller's explicit return-variant limit. */
/** Retain every projected return variant. */
export const retainJavaScriptExportShapes = (
selection: SelectedJavaScriptExport,
maximum: number,
): RetainedJavaScriptExportShapes => {
const shapes = selection.projection?.static_return_shapes ?? [];
const retained = shapes.slice(0, maximum);
return { shapes: retained, omitted: shapes.length - retained.length };
return { shapes, omitted: 0 };
};
const selectorBase = (
side: JavaScriptExportShapeSideInput,
candidates: readonly ExportCandidate[],
retained: readonly ExportCandidate[],
): SelectorBase => ({
evidence_id: side.evidenceId,
graph_id: side.graph.graph_id,
requested_module_path: side.modulePath,
requested_export_name: side.exportName,
candidates: retained.map((candidate) => ({
candidates: candidates.map((candidate) => ({
node_id: candidate.node.node_id,
module_path: candidate.modulePath,
export_name: candidate.exportName,
matches_requested_module: candidate.modulePath === side.modulePath,
matches_requested_export: candidate.exportName === side.exportName,
})),
omitted_candidates: candidates.length - retained.length,
omitted_candidates: 0,
});
const unresolvedSelection = (
-1
View File
@@ -214,7 +214,6 @@ const runtimeFileIdentitySchema = z
const replayPlanSchema = z
.object({
schema_version: z.literal(1),
plan_digest: digestSchema,
policy_version: z.literal("linux-bwrap-systemd-v1"),
policy_sha256: digestSchema,
@@ -15,7 +15,6 @@ import {
type JavaScriptSemanticGraphNode,
} from "./javascriptSemanticGraph.js";
import { queryJavaScriptSemanticGraph } from "./javascriptSemanticQuery.js";
import { javaScriptSemanticQueryInputSchema } from "./javascriptSemanticQuerySchemas.js";
const SHA = "a".repeat(64);
const JAG_ID = `jag_${"b".repeat(64)}`;
@@ -244,70 +243,35 @@ const graphWithCandidateEdge = (): JavaScriptSemanticGraph => {
});
};
const graphWithDuplicateLiteral = (): JavaScriptSemanticGraph => {
const graph = fixtureGraph();
const duplicate = node("literal", "literal-copy", { value: "TOKEN" });
const { graph_id: _graphId, ...input } = graph;
return createJavaScriptSemanticGraph({
...input,
nodes: [...graph.nodes, duplicate],
});
};
it("bounds relevant unknown frontiers independently from relation pages", () => {
it("returns every relevant unknown inline", () => {
const graph = fixtureGraph(true);
const callable = graph.nodes.find(({ kind }) => kind === "function");
if (callable === undefined) throw new Error("Expected callable node");
const result = queryJavaScriptSemanticGraph(graph, {
seed: { kind: "semantic-node", node_id: callable.node_id },
direction: "forward-influence",
limits: { max_unknowns: 0 },
});
expect(result).toMatchObject({
status: "truncated",
unknowns: [],
summary: { relevant_unknowns: 1, retained_unknowns: 0 },
coverage: { status: "truncated" },
summary: { relevant_unknowns: 1, retained_unknowns: 1 },
});
expect(result.unknowns).toHaveLength(1);
});
it("traces deterministic forward influence and pages without changing identity", () => {
it("returns the complete deterministic forward influence result inline", () => {
const graph = fixtureGraph();
const input = {
seed: { kind: "literal" as const, value: "TOKEN" },
direction: "forward-influence" as const,
expected: { role: "sink" as const, classes: ["request" as const] },
limits: {
max_seed_matches: 25,
max_nodes: 20,
max_edges: 20,
max_depth: 12,
max_functions: 10,
max_modules: 10,
page_size: 2,
},
};
const first = queryJavaScriptSemanticGraph(graph, input);
expect(first).toMatchObject({
status: "found",
summary: { traversed_nodes: 4, traversed_relations: 3 },
page: { offset: 0, size: 2 },
relations: expect.any(Array),
});
expect(first.expected_match_node_ids).toHaveLength(1);
expect(first.page.next_cursor).not.toBeNull();
const second = queryJavaScriptSemanticGraph(graph, {
...input,
cursor: first.page.next_cursor,
});
expect(second.query_id).toBe(first.query_id);
expect(second.page).toMatchObject({
offset: 2,
size: 1,
next_cursor: null,
});
expect(second.relations[0]?.relation_id).not.toBe(
first.relations[0]?.relation_id,
);
expect(first.relations).toHaveLength(3);
});
it("keeps relevant dynamic frontiers unknown", () => {
@@ -343,103 +307,3 @@ it("excludes candidate edges unless the caller explicitly opts in", () => {
{ relation: "supplies-request-field", resolution: "candidate" },
]);
});
it("bounds seed ambiguity before traversal", () => {
const result = queryJavaScriptSemanticGraph(graphWithDuplicateLiteral(), {
seed: { kind: "literal", value: "TOKEN" },
direction: "forward-influence",
limits: {
max_seed_matches: 1,
max_nodes: 20,
max_edges: 20,
max_depth: 12,
max_functions: 10,
max_modules: 10,
page_size: 10,
},
});
expect(result.summary).toMatchObject({
total_seed_matches: 2,
retained_seed_matches: 1,
});
expect(result.status).toBe("truncated");
expect(result.coverage.frontier).toContainEqual(
expect.objectContaining({ reason: "max-seed-matches", depth: 0 }),
);
});
it("reports exact caller limits and a deterministic truncation frontier", () => {
const result = queryJavaScriptSemanticGraph(fixtureGraph(), {
seed: { kind: "literal", value: "TOKEN" },
direction: "forward-influence",
limits: {
max_seed_matches: 1,
max_nodes: 2,
max_edges: 10,
max_depth: 12,
max_functions: 10,
max_modules: 10,
page_size: 10,
},
});
expect(result.status).toBe("truncated");
expect(result.coverage.frontier).toMatchObject([{ reason: "max-nodes" }]);
expect(result.applied_limits.max_nodes).toBe(2);
expect(result.accepted_limit_ranges.max_nodes).toEqual({
minimum: 1,
maximum: 50_000,
});
});
it.each([
{
limit: { max_edges: 1 },
reason: "max-edges",
},
{
limit: { max_depth: 0 },
reason: "max-depth",
},
])("reports the $reason frontier", ({ limit, reason }) => {
const result = queryJavaScriptSemanticGraph(fixtureGraph(), {
seed: { kind: "literal", value: "TOKEN" },
direction: "forward-influence",
limits: {
max_seed_matches: 10,
max_nodes: 20,
max_edges: limit.max_edges ?? 20,
max_depth: limit.max_depth ?? 12,
max_functions: 10,
max_modules: 10,
page_size: 10,
},
});
expect(result.status).toBe("truncated");
expect(result.coverage.frontier).toContainEqual(
expect.objectContaining({ reason }),
);
});
it("rejects out-of-range limits and cursors from another query", () => {
expect(() =>
javaScriptSemanticQueryInputSchema.parse({
seed: { kind: "literal", value: "TOKEN" },
direction: "forward-influence",
limits: { max_nodes: 50_001 },
}),
).toThrow();
const graph = fixtureGraph();
const first = queryJavaScriptSemanticGraph(graph, {
seed: { kind: "literal", value: "TOKEN" },
direction: "forward-influence",
limits: { page_size: 1 },
});
expect(() =>
queryJavaScriptSemanticGraph(graph, {
seed: { kind: "endpoint", value: "https://example.invalid/v1" },
direction: "backward-provenance",
limits: { page_size: 1 },
cursor: first.page.next_cursor,
}),
).toThrow(/cursor does not match/u);
});
+31 -84
View File
@@ -18,13 +18,20 @@ import {
} from "./javascriptSemanticQueryAssessment.js";
import {
canonicalJavaScriptSemanticQueryJson as canonicalJson,
createJavaScriptSemanticQueryCursor as createCursor,
JavaScriptSemanticQueryCursorError,
javaScriptSemanticQueryIdentifier as queryIdentifier,
parseJavaScriptSemanticQueryCursor as parseCursor,
resolveJavaScriptSemanticQuerySeeds as resolveSeeds,
} from "./javascriptSemanticQueryIdentity.js";
const TRAVERSAL_LIMITS = {
max_seed_matches: 1_000,
max_nodes: 50_000,
max_edges: 100_000,
max_depth: 64,
max_functions: 10_000,
max_modules: 10_000,
max_unknowns: 10_000,
} as const;
interface TraversalEntry {
readonly relation: JavaScriptSemanticGraphRelation;
readonly nextNodeId: string;
@@ -45,21 +52,9 @@ interface SeedAdmission {
readonly frontier: QueryFrontier[];
}
interface QueryPage {
interface QueryResult {
readonly retainedNodes: JavaScriptSemanticGraphNode[];
readonly retainedRelations: JavaScriptSemanticGraphRelation[];
readonly pageRelations: JavaScriptSemanticGraphRelation[];
readonly pageNodes: JavaScriptSemanticGraphNode[];
readonly nextCursor: string | null;
}
interface QueryPageInput {
readonly graph: JavaScriptSemanticGraph;
readonly traversal: Traversal;
readonly query: JavaScriptSemanticQueryInput;
readonly queryId: string;
readonly offset: number;
readonly retainedSeeds: readonly string[];
}
/** Run one deterministic bounded traversal over a verified semantic graph. */
@@ -69,7 +64,6 @@ export const queryJavaScriptSemanticGraph = (
): JavaScriptSemanticQueryResult => {
const input = javaScriptSemanticQueryInputSchema.parse(rawInput);
const queryId = queryIdentifier(graph, input);
const offset = parseCursor(input.cursor, queryId);
const seeds = resolveSeeds(graph, input);
const admission = admitSeeds(graph, seeds, input);
const retainedSeeds = admission.nodeIds;
@@ -79,14 +73,7 @@ export const queryJavaScriptSemanticGraph = (
...traversal.frontier,
...admission.frontier,
]);
const page = createQueryPage({
graph,
traversal,
query: input,
queryId,
offset,
retainedSeeds,
});
const result = createQueryResult(graph, traversal);
const allRelevantUnknowns = relevantUnknownFrontiers(
graph,
traversal.nodeIds,
@@ -94,7 +81,7 @@ export const queryJavaScriptSemanticGraph = (
);
const relevantUnknowns = allRelevantUnknowns.slice(
0,
input.limits.max_unknowns,
TRAVERSAL_LIMITS.max_unknowns,
);
const candidateRelations = relevantCandidateRelationCount(
graph,
@@ -102,8 +89,8 @@ export const queryJavaScriptSemanticGraph = (
input,
);
const expectedMatches = expectedMatchesFor(
page.retainedNodes,
page.retainedRelations,
result.retainedNodes,
result.retainedRelations,
input,
);
const assessment = assessJavaScriptSemanticQuery({
@@ -124,8 +111,8 @@ export const queryJavaScriptSemanticGraph = (
direction: input.direction,
status: assessment.status,
seed_node_ids: retainedSeeds,
nodes: page.pageNodes,
relations: page.pageRelations,
nodes: result.retainedNodes,
relations: result.retainedRelations,
unknowns: relevantUnknowns,
expected_match_node_ids: expectedMatches.map(({ node_id }) => node_id),
summary: {
@@ -139,60 +126,21 @@ export const queryJavaScriptSemanticGraph = (
retained_unknowns: relevantUnknowns.length,
},
coverage: assessment.coverage,
page: {
offset,
size: page.pageRelations.length,
next_cursor: page.nextCursor,
},
applied_limits: input.limits,
accepted_limit_ranges: assessment.acceptedLimitRanges,
limitations: assessment.limitations,
});
};
const createQueryPage = ({
graph,
traversal,
query,
queryId,
offset,
retainedSeeds,
}: QueryPageInput): QueryPage => {
const createQueryResult = (
graph: JavaScriptSemanticGraph,
traversal: Traversal,
): QueryResult => {
const retainedNodes = graph.nodes.filter(({ node_id }) =>
traversal.nodeIds.has(node_id),
);
const retainedRelations = graph.relations.filter(({ relation_id }) =>
traversal.relationIds.has(relation_id),
);
if (offset > retainedRelations.length)
throw new JavaScriptSemanticQueryCursorError(
"Semantic query cursor is beyond the retained result",
);
const pageRelations = retainedRelations.slice(
offset,
offset + query.limits.page_size,
);
const pageNodeIds = new Set([
...retainedSeeds,
...pageRelations.flatMap(({ source_node_id, target_node_id }) => [
source_node_id,
target_node_id,
]),
]);
const pageNodes = graph.nodes.filter(({ node_id }) =>
pageNodeIds.has(node_id),
);
const nextOffset = offset + pageRelations.length;
return {
retainedNodes,
retainedRelations,
pageRelations,
pageNodes,
nextCursor:
nextOffset < retainedRelations.length
? createCursor(queryId, nextOffset)
: null,
};
return { retainedNodes, retainedRelations };
};
const admitSeeds = (
@@ -244,20 +192,19 @@ const seedBlockedReason = (
node: JavaScriptSemanticGraphNode,
bounds: SeedBounds,
): QueryFrontier["reason"] | null => {
if (bounds.retained.length >= bounds.input.limits.max_seed_matches)
if (bounds.retained.length >= TRAVERSAL_LIMITS.max_seed_matches)
return "max-seed-matches";
if (bounds.retained.length >= bounds.input.limits.max_nodes)
return "max-nodes";
if (bounds.retained.length >= TRAVERSAL_LIMITS.max_nodes) return "max-nodes";
const functionId = functionOwner(node);
if (
functionId !== null &&
!bounds.functions.has(functionId) &&
bounds.functions.size >= bounds.input.limits.max_functions
bounds.functions.size >= TRAVERSAL_LIMITS.max_functions
)
return "max-functions";
if (
!bounds.modules.has(node.identity.module_path) &&
bounds.modules.size >= bounds.input.limits.max_modules
bounds.modules.size >= TRAVERSAL_LIMITS.max_modules
)
return "max-modules";
return null;
@@ -407,26 +354,26 @@ const blockedReason = (
depth: number,
bounds: TraversalBounds,
): QueryFrontier["reason"] | null => {
if (depth >= bounds.input.limits.max_depth) return "max-depth";
if (depth >= TRAVERSAL_LIMITS.max_depth) return "max-depth";
if (
!bounds.relationIds.has(entry.relation.relation_id) &&
bounds.relationIds.size >= bounds.input.limits.max_edges
bounds.relationIds.size >= TRAVERSAL_LIMITS.max_edges
)
return "max-edges";
if (bounds.nodeIds.has(entry.nextNodeId)) return null;
if (bounds.nodeIds.size >= bounds.input.limits.max_nodes) return "max-nodes";
if (bounds.nodeIds.size >= TRAVERSAL_LIMITS.max_nodes) return "max-nodes";
const node = bounds.nodes.get(entry.nextNodeId);
if (node === undefined) return null;
const functionId = functionOwner(node);
if (
functionId !== null &&
!bounds.functionIds.has(functionId) &&
bounds.functionIds.size >= bounds.input.limits.max_functions
bounds.functionIds.size >= TRAVERSAL_LIMITS.max_functions
)
return "max-functions";
if (
!bounds.modules.has(node.identity.module_path) &&
bounds.modules.size >= bounds.input.limits.max_modules
bounds.modules.size >= TRAVERSAL_LIMITS.max_modules
)
return "max-modules";
return null;
@@ -34,7 +34,6 @@ export interface JavaScriptSemanticQueryAssessment {
readonly status: JavaScriptSemanticQueryResult["status"];
readonly coverage: JavaScriptSemanticQueryResult["coverage"];
readonly limitations: string[];
readonly acceptedLimitRanges: JavaScriptSemanticQueryResult["accepted_limit_ranges"];
}
/** Classify a bounded semantic traversal without promoting unknowns to absence. */
@@ -44,16 +43,6 @@ export const assessJavaScriptSemanticQuery = (
status: queryStatus(input),
coverage: queryCoverage(input),
limitations: queryLimitations(input),
acceptedLimitRanges: {
max_seed_matches: { minimum: 1, maximum: 1_000 },
max_nodes: { minimum: 1, maximum: 50_000 },
max_edges: { minimum: 1, maximum: 100_000 },
max_depth: { minimum: 0, maximum: 64 },
max_functions: { minimum: 1, maximum: 10_000 },
max_modules: { minimum: 1, maximum: 10_000 },
max_unknowns: { minimum: 0, maximum: 10_000 },
page_size: { minimum: 1, maximum: 1_000 },
},
});
const queryCoverage = (
@@ -98,12 +87,12 @@ const queryLimitations = (
...(input.totalSeeds === input.retainedSeeds
? []
: [
"Seed matches exceeded caller bounds; omitted starts remain unexplored.",
"Seed matches exceeded the internal traversal safety ceiling; omitted starts remain unexplored.",
]),
...(input.frontier.length === 0
? []
: [
"Traversal stopped at explicit caller limits; frontier facts remain unknown.",
"Traversal stopped at an internal resource ceiling; frontier facts remain unknown.",
]),
...(input.unknowns.length === 0
? []
@@ -112,7 +101,7 @@ const queryLimitations = (
]),
...(input.unknownsTruncated
? [
"Relevant unknown frontiers exceeded the caller limit; omitted unknowns remain unresolved.",
"Relevant unknown frontiers exceeded the internal result safety ceiling; omitted unknowns remain unresolved.",
]
: []),
...(input.candidateRelations === 0
+2 -36
View File
@@ -9,9 +9,6 @@ import type {
} from "./javascriptSemanticGraph.js";
import type { JavaScriptSemanticQueryInput } from "./javascriptSemanticQuerySchemas.js";
/** Caller cursor that cannot identify a page of the current semantic query. */
export class JavaScriptSemanticQueryCursorError extends TypeError {}
/** Canonical JSON used by semantic query commitments and ordering. */
export const canonicalJavaScriptSemanticQueryJson = (
value: unknown,
@@ -32,12 +29,12 @@ const digest = (value: unknown): string =>
const uniqueSorted = (values: readonly string[]): string[] =>
[...new Set(values)].sort(compareCodePoints);
/** Derive the stable identity for one graph-bound query without its cursor. */
/** Derive the stable identity for one graph-bound query. */
export const javaScriptSemanticQueryIdentifier = (
graph: JavaScriptSemanticGraph,
input: JavaScriptSemanticQueryInput,
): string => {
const { cursor: _cursor, ...semantic } = input;
const semantic = input;
return `jsrq_${digest({
source_graph_id: graph.graph_id,
...semantic,
@@ -55,37 +52,6 @@ export const javaScriptSemanticQueryIdentifier = (
})}`;
};
/** Commit one deterministic relation-page offset to a query identity. */
export const createJavaScriptSemanticQueryCursor = (
queryId: string,
offset: number,
): string => `jsrqc_${String(offset)}_${digest({ query_id: queryId, offset })}`;
/** Parse a query-bound cursor and reject stale, malformed, or foreign values. */
export const parseJavaScriptSemanticQueryCursor = (
cursor: string | null,
queryId: string,
): number => {
if (cursor === null) return 0;
const match = /^jsrqc_([0-9]+)_([a-f0-9]{64})$/u.exec(cursor);
const offsetText = match?.[1];
const commitment = match?.[2];
if (offsetText === undefined || commitment === undefined)
throw new JavaScriptSemanticQueryCursorError(
"Semantic query cursor is malformed",
);
const offset = Number(offsetText);
if (
!Number.isSafeInteger(offset) ||
offset < 0 ||
commitment !== digest({ query_id: queryId, offset })
)
throw new JavaScriptSemanticQueryCursorError(
"Semantic query cursor does not match this query",
);
return offset;
};
/** Resolve deterministic seed candidates without applying traversal limits. */
export const resolveJavaScriptSemanticQuerySeeds = (
graph: JavaScriptSemanticGraph,
+1 -71
View File
@@ -57,18 +57,6 @@ export const javaScriptSemanticQuerySeedSchema = z.discriminatedUnion("kind", [
}),
]);
/** Strict caller limits for deterministic bounded traversal. */
export const javaScriptSemanticQueryLimitsSchema = z.strictObject({
max_seed_matches: z.number().int().min(1).max(1_000).default(25),
max_nodes: z.number().int().min(1).max(50_000).default(2_000),
max_edges: z.number().int().min(1).max(100_000).default(4_000),
max_depth: z.number().int().min(0).max(64).default(12),
max_functions: z.number().int().min(1).max(10_000).default(500),
max_modules: z.number().int().min(1).max(10_000).default(500),
max_unknowns: z.number().int().min(0).max(10_000).default(1_000),
page_size: z.number().int().min(1).max(1_000).default(200),
});
const sourceMapAuthoritySchema = z.strictObject({
authority: z.literal("none"),
});
@@ -96,21 +84,6 @@ export const javaScriptSemanticQueryInputSchema = z.strictObject({
.nullable()
.default(null),
source_map_authority: sourceMapAuthoritySchema.default({ authority: "none" }),
limits: javaScriptSemanticQueryLimitsSchema.default({
max_seed_matches: 25,
max_nodes: 2_000,
max_edges: 4_000,
max_depth: 12,
max_functions: 500,
max_modules: 500,
max_unknowns: 1_000,
page_size: 200,
}),
cursor: z
.string()
.regex(/^jsrqc_[0-9]+_[a-f0-9]{64}$/u)
.nullable()
.default(null),
});
const queryFrontierSchema = z.strictObject({
@@ -126,7 +99,7 @@ const queryFrontierSchema = z.strictObject({
]),
});
/** Deterministic, pageable semantic trace result. */
/** Deterministic semantic trace result. */
export const javaScriptSemanticQueryResultSchema = z.strictObject({
query_id: z.string().regex(/^jsrq_[a-f0-9]{64}$/u),
source_graph_id: z.string().regex(/^jsrg_[a-f0-9]{64}$/u),
@@ -159,49 +132,6 @@ export const javaScriptSemanticQueryResultSchema = z.strictObject({
status: z.enum(["complete", "partial", "truncated", "unavailable"]),
frontier: z.array(queryFrontierSchema).max(100_000),
}),
page: z.strictObject({
offset: z.number().int().min(0),
size: z.number().int().min(0).max(1_000),
next_cursor: z
.string()
.regex(/^jsrqc_[0-9]+_[a-f0-9]{64}$/u)
.nullable(),
}),
applied_limits: javaScriptSemanticQueryLimitsSchema,
accepted_limit_ranges: z.strictObject({
max_seed_matches: z.strictObject({
minimum: z.literal(1),
maximum: z.literal(1_000),
}),
max_nodes: z.strictObject({
minimum: z.literal(1),
maximum: z.literal(50_000),
}),
max_edges: z.strictObject({
minimum: z.literal(1),
maximum: z.literal(100_000),
}),
max_depth: z.strictObject({
minimum: z.literal(0),
maximum: z.literal(64),
}),
max_functions: z.strictObject({
minimum: z.literal(1),
maximum: z.literal(10_000),
}),
max_modules: z.strictObject({
minimum: z.literal(1),
maximum: z.literal(10_000),
}),
max_unknowns: z.strictObject({
minimum: z.literal(0),
maximum: z.literal(10_000),
}),
page_size: z.strictObject({
minimum: z.literal(1),
maximum: z.literal(1_000),
}),
}),
limitations: z.array(z.string().min(1).max(4_096)).max(1_000),
});
@@ -283,22 +283,6 @@ export const reconstructionObligationLedgerInputSchema = z.strictObject({
evidence_bundle: evidenceBundleSchema,
reviewed_obligations: z.array(reviewedReconstructionObligationSchema),
manifest: reconstructionObligationManifestSchema,
page: z.strictObject({
offset: z.number().int().min(0).max(10_000),
limit: z.number().int().min(1).max(100),
}),
});
export const reconstructionObligationLedgerPageSchema = z.strictObject({
...reconstructionObligationLedgerSchema.omit({ obligations: true }).shape,
page: z.strictObject({
offset: z.number().int().min(0),
limit: z.number().int().min(1).max(100),
total: z.number().int().min(0),
returned: z.number().int().min(0),
next_offset: z.number().int().min(0).nullable(),
}),
obligations: z.array(reconstructionObligationSchema).max(100),
});
export type ReconstructionObligation = z.infer<
@@ -310,9 +294,6 @@ export type ReconstructionObligationLedger = z.infer<
export type ReconstructionObligationLedgerInput = z.infer<
typeof reconstructionObligationLedgerInputSchema
>;
export type ReconstructionObligationLedgerPage = z.infer<
typeof reconstructionObligationLedgerPageSchema
>;
export type ReviewedReconstructionObligation = z.infer<
typeof reviewedReconstructionObligationSchema
>;
+3 -6
View File
@@ -66,14 +66,11 @@ export const reconstructionClaimUnknowns = (
);
};
/** Aggregate and bound deterministic probes for unresolved claims. */
/** Aggregate deterministic probes for unresolved claims. */
export const reconstructionProbes = (
results: readonly ReconstructionClaimResult[],
heads: ReadonlyMap<string, ResidualUnknown>,
): {
readonly items: ReconstructionVerificationResult["recommended_probes"];
readonly truncated: boolean;
} => {
): ReconstructionVerificationResult["recommended_probes"] => {
const output = new Map<
string,
{
@@ -111,7 +108,7 @@ export const reconstructionProbes = (
"en",
),
);
return { items: sorted.slice(0, 2_000), truncated: sorted.length > 2_000 };
return sorted;
};
const defaultProbe = (
+35 -38
View File
@@ -118,19 +118,35 @@ const behavioralSpec = (comparison: Evidence) => ({
],
});
const artifactResult = (partial = false): JsonValue => ({
status: "unchanged",
const artifactResult = (
partial = false,
evidenceLinks: readonly string[] = [],
): JsonValue => ({
status: partial ? "truncated" : "unchanged",
left_manifest_id: `agm_${"3".repeat(64)}`,
right_manifest_id: `agm_${"4".repeat(64)}`,
summary: { unchanged: 1, added: 0, removed: 0, changed: 0, unknown: 0 },
changes: {
items: [],
offset: 0,
limit: 100,
total: partial ? 1 : 0,
next_offset: partial ? 1 : null,
summary: {
unchanged: partial ? 0 : 1,
added: 0,
removed: 0,
changed: 0,
unknown: partial ? 1 : 0,
},
limitations: [],
changes: partial
? [
{
classification: "unknown",
logical_path: "unavailable",
dimensions: ["availability"],
left_occurrence_id: null,
right_occurrence_id: null,
left_artifact_id: null,
right_artifact_id: null,
evidence_links: [...evidenceLinks],
},
]
: [],
limitations: partial ? ["Inventory evidence is incomplete."] : [],
});
const artifactComparison = (
@@ -152,7 +168,7 @@ const artifactComparison = (
left_evidence_ids: [left.evidence_id],
right_evidence_ids: [right.evidence_id],
},
result: artifactResult(partial),
result: artifactResult(partial, [left.evidence_id, right.evidence_id]),
confidence: "derived",
authority: "analyst-inference",
evidenceLinks: [left.evidence_id, right.evidence_id],
@@ -207,8 +223,6 @@ describe("reconstruction verification", () => {
],
},
createEvidenceBundle([comparison, right, left]),
0,
100,
),
).toMatchObject({
summary: { total: 2, passed: 1, failed: 1, unknown: 0 },
@@ -222,13 +236,11 @@ describe("reconstruction verification", () => {
const result = verifyReconstruction(
behavioralSpec(comparison),
createEvidenceBundle([comparison, right, left]),
0,
100,
);
expect(reconstructionVerificationResultSchema.parse(result)).toMatchObject({
status: "pass",
summary: { total: 1, passed: 1, failed: 0, unknown: 0 },
claims: { total: 1, next_offset: null },
claims: { items: expect.any(Array) },
});
expect(
reconstructionVerificationResultSchema.safeParse({
@@ -258,8 +270,6 @@ describe("reconstruction verification", () => {
const result = verifyReconstruction(
behavioralSpec(comparison),
createEvidenceBundle([left, right, comparison]),
0,
100,
);
expect(result).toMatchObject({
status: "fail",
@@ -276,8 +286,6 @@ describe("reconstruction verification", () => {
verifyReconstruction(
behavioralSpec(runtime),
createEvidenceBundle([weakLeft, replayRight, runtime]),
0,
100,
).status,
).toBe("unknown");
@@ -298,8 +306,6 @@ describe("reconstruction verification", () => {
],
},
createEvidenceBundle([left, comparison, right]),
0,
100,
);
expect(result.status).toBe("unknown");
expect(result.recommended_probes[0]?.operation).toBe("inventory_artifact");
@@ -318,13 +324,11 @@ describe("reconstruction verification integrity", () => {
verifyReconstruction(
behavioralSpec(comparison),
createEvidenceBundle([left, right, extra, comparison]),
0,
100,
),
).toThrow(/closure disagrees/u);
});
it("pages claims deterministically and gives specification a stable digest", () => {
it("returns every claim deterministically and gives specification a stable digest", () => {
const left = source("1", "controlled-replay");
const right = source("2", "controlled-replay");
const comparison = processComparison(left, right, processResult());
@@ -342,8 +346,6 @@ describe("reconstruction verification integrity", () => {
const first = verifyReconstruction(
{ name: "Compatibility", claims },
bundle,
0,
1,
);
const repeated = verifyReconstruction(
{
@@ -351,15 +353,15 @@ describe("reconstruction verification integrity", () => {
claims: [...claims].reverse(),
},
bundle,
0,
1,
);
expect(first).toEqual(repeated);
expect(first.claims.items[0]?.claim_id).toBe("terminal-output");
expect(first.claims.next_offset).toBe(1);
expect(first.claims.items.map(({ claim_id }) => claim_id)).toEqual([
"terminal-output",
"z-exit",
]);
});
it("keeps all active unknowns gating while bounding their projection", () => {
it("returns all active unknowns while keeping them gating", () => {
const left = source("1", "controlled-replay");
const right = source("2", "controlled-replay");
const comparison = processComparison(left, right, processResult());
@@ -400,13 +402,8 @@ describe("reconstruction verification integrity", () => {
const result = verifyReconstruction(
behavioralSpec(comparison),
createEvidenceBundle([left, right, comparison, ...mutations], unknowns),
0,
100,
);
expect(result.status).toBe("unknown");
expect(result.claims.items[0]?.unknown_ids).toHaveLength(100);
expect(result.claims.items[0]?.limitations).toContain(
"1 additional active residual unknowns affect this claim but are omitted by the 100-item display limit.",
);
expect(result.claims.items[0]?.unknown_ids).toHaveLength(101);
});
});
+4 -27
View File
@@ -73,8 +73,6 @@ const acceptsPredicate = (
export const verifyReconstruction = (
specificationInput: unknown,
bundleInput: unknown,
offset: number,
limit: number,
): ReconstructionVerificationResult => {
const specification =
reconstructionSpecificationSchema.parse(specificationInput);
@@ -91,7 +89,6 @@ export const verifyReconstruction = (
const results = specification.claims
.map((claim) => evaluateClaim(claim, records, heads))
.sort((left, right) => left.claim_id.localeCompare(right.claim_id, "en"));
const page = results.slice(offset, offset + limit);
const failed = count(results, "fail");
const unresolved = count(results, "unknown");
const probes = reconstructionProbes(results, heads);
@@ -117,20 +114,12 @@ export const verifyReconstruction = (
structural: results.filter(({ kind }) => kind !== "behavioral").length,
},
claims: {
items: page,
offset,
limit,
total: results.length,
next_offset:
offset + page.length < results.length ? offset + page.length : null,
items: results,
},
recommended_probes: probes.items,
recommended_probes: probes,
evidence_links: evidenceLinks,
limitations: [
"Pass means every declared claim passed; it does not establish global implementation equivalence.",
...(probes.truncated
? ["Recommended probes were truncated at the 2,000-item limit."]
: []),
],
};
return reconstructionVerificationResultSchema.parse(output);
@@ -172,8 +161,7 @@ const evaluateClaim = (
allSourceIds,
);
const status = classify(observed, limitations, relevantUnknowns);
const displayedUnknowns = relevantUnknowns.slice(0, 100);
const hiddenUnknownCount = relevantUnknowns.length - displayedUnknowns.length;
const displayedUnknowns = relevantUnknowns;
return reconstructionClaimResultSchema.parse({
claim_id: claim.claim_id,
kind: claim.kind,
@@ -191,11 +179,6 @@ const evaluateClaim = (
...displayedUnknowns.map(
({ question }) => `Residual unknown: ${question}`,
),
...(hiddenUnknownCount > 0
? [
`${hiddenUnknownCount} additional active residual unknowns affect this claim but are omitted by the 100-item display limit.`,
]
: []),
]),
});
};
@@ -307,13 +290,7 @@ const observedStatus = (claim: Claim, evidence: Evidence): ObservedStatus => {
evidence.normalized_result,
);
if (
result.changes.offset !== 0 ||
result.changes.next_offset !== null ||
result.changes.items.length !== result.changes.total
)
return "unknown";
if (
result.changes.items.some((item) =>
result.changes.some((item) =>
item.evidence_links.some((id) => !evidence.evidence_links.includes(id)),
)
)
+18 -26
View File
@@ -57,7 +57,7 @@ const reconstructionClaimSchema = z.discriminatedUnion("kind", [
export const reconstructionSpecificationSchema = z
.object({
name: z.string().trim().min(1).max(200),
claims: z.array(reconstructionClaimSchema).min(1).max(100),
claims: z.array(reconstructionClaimSchema).min(1),
})
.superRefine((value, context) => {
const ids = value.claims.map(({ claim_id: id }) => id);
@@ -85,8 +85,6 @@ export const reconstructionVerificationInputSchema = z.strictObject({
boundary_id: z.string().min(1).max(200),
})
.optional(),
offset: z.number().int().min(0).default(0),
limit: z.number().int().min(1).max(100).default(100),
unknown_registry_approved: z.literal(true).optional(),
});
@@ -105,20 +103,20 @@ export const reconstructionClaimResultSchema = z.object({
"contradiction",
]),
comparison_evidence_id: evidenceIdSchema,
left_evidence_ids: z.array(evidenceIdSchema).min(1).max(100),
right_evidence_ids: z.array(evidenceIdSchema).min(1).max(100),
left_evidence_ids: z.array(evidenceIdSchema).min(1),
right_evidence_ids: z.array(evidenceIdSchema).min(1),
evidence_links: z.array(evidenceIdSchema).min(3).max(201),
unknown_ids: z.array(unknownIdSchema).max(100),
unknown_ids: z.array(unknownIdSchema),
limitations: z.array(z.string()),
});
/** Evidence-cited result over every declared claim, independently paged. */
/** Evidence-cited result over every declared claim. */
export const reconstructionVerificationResultSchema = z
.object({
status: verificationStatusSchema,
specification_sha256: digestSchema,
summary: z.object({
total: z.number().int().min(1).max(100),
total: z.number().int().min(1),
passed: z.number().int().min(0),
failed: z.number().int().min(0),
unknown: z.number().int().min(0),
@@ -126,22 +124,16 @@ export const reconstructionVerificationResultSchema = z
structural: z.number().int().min(0),
}),
claims: z.object({
items: z.array(reconstructionClaimResultSchema).max(100),
offset: z.number().int().min(0),
limit: z.number().int().min(1).max(100),
total: z.number().int().min(1).max(100),
next_offset: z.number().int().min(0).nullable(),
items: z.array(reconstructionClaimResultSchema),
}),
recommended_probes: z
.array(
z.object({
operation: z.string().min(1).max(200),
rationale: z.string().min(1).max(1_000),
claim_ids: z.array(claimIdSchema).min(1).max(100),
unknown_ids: z.array(unknownIdSchema).max(100),
}),
)
.max(2_000),
recommended_probes: z.array(
z.object({
operation: z.string().min(1).max(200),
rationale: z.string().min(1).max(1_000),
claim_ids: z.array(claimIdSchema).min(1),
unknown_ids: z.array(unknownIdSchema),
}),
),
evidence_links: z.array(evidenceIdSchema).min(3).max(20_100),
limitations: z.array(z.string()),
})
@@ -161,9 +153,9 @@ export const reconstructionVerificationResultSchema = z
["summary", "total"],
],
[
result.claims.total !== summary.total,
"Paged claim total must equal the summary total",
["claims", "total"],
result.claims.items.length !== summary.total,
"Retained claims must equal the summary total",
["claims", "items"],
],
[
result.status !== expectedStatus,
@@ -10,7 +10,6 @@ const digest = (character: string): string => character.repeat(64);
describe("provider-neutral runtime characterization plan", () => {
it("commits preparation separately from execution approval", () => {
const plan = createRuntimeCharacterizationPlan({
schema_version: 1,
preparation_sha256: digest("5"),
artifact: {
path: "/approved/app.js",
-1
View File
@@ -33,7 +33,6 @@ const callableIdentitySchema = z.strictObject({
});
export const runtimeCharacterizationPlanSchema = z.strictObject({
schema_version: z.literal(1),
plan_sha256: digestSchema,
preparation_sha256: digestSchema,
artifact: artifactIdentitySchema,
+2 -7
View File
@@ -132,7 +132,6 @@ describe("static/runtime correlation", () => {
status: "correlated",
summary: { hypotheses: 1, contradictions: 0, unresolved: 0 },
correlations: {
total: 1,
items: [
{
observed_pattern: "cochanged",
@@ -187,7 +186,7 @@ describe("static/runtime correlation", () => {
});
});
it("is order-stable, paginated, and rejects duplicates and absent selectors", () => {
it("returns all correlations order-stably and rejects duplicates and absent selectors", () => {
const runtime = processComparison("changed");
const base = mapping(runtime, "cochanged");
const second = {
@@ -201,17 +200,13 @@ describe("static/runtime correlation", () => {
const forward = correlateStaticAndRuntime({
...base,
mappings: [base.mappings[0], second],
offset: 0,
limit: 1,
});
const reversed = correlateStaticAndRuntime({
...base,
mappings: [second, base.mappings[0]],
offset: 0,
limit: 1,
});
expect(reversed).toEqual(forward);
expect(forward.correlations).toMatchObject({ total: 2, next_offset: 1 });
expect(forward.correlations.items).toHaveLength(2);
expect(() =>
correlateStaticAndRuntime({
...base,
+5 -41
View File
@@ -88,8 +88,6 @@ export const staticRuntimeCorrelationInputSchema = z
static_comparisons: z.array(evidenceSchema).min(1).max(100),
runtime_comparisons: z.array(evidenceSchema).min(1).max(100),
mappings: z.array(mappingSchema).min(1).max(500),
offset: z.number().int().min(0).default(0),
limit: z.number().int().min(1).max(100).default(100),
unknown_registry_approved: z.literal(true).optional(),
})
.strict();
@@ -122,7 +120,7 @@ const correlationItemSchema = z.object({
limitations: z.array(z.string()),
});
/** Deterministic, paginated static/runtime correlation payload. */
/** Deterministic static/runtime correlation payload. */
export const staticRuntimeCorrelationResultSchema = z.object({
status: z.enum(["correlated", "contradicted", "unknown", "truncated"]),
summary: z.object({
@@ -131,11 +129,7 @@ export const staticRuntimeCorrelationResultSchema = z.object({
unresolved: z.number().int().min(0),
}),
correlations: z.object({
items: z.array(correlationItemSchema).max(100),
offset: z.number().int().min(0),
limit: z.number().int().min(1).max(100),
total: z.number().int().min(0),
next_offset: z.number().int().min(0).nullable(),
items: z.array(correlationItemSchema),
}),
evidence_links: z.array(evidenceIdSchema).min(2).max(20_100),
limitations: z.array(z.string()),
@@ -191,7 +185,6 @@ export const correlateStaticAndRuntime = (
throw new TypeError(
"Static/runtime correlation rejects duplicate mappings",
);
const page = correlations.slice(parsed.offset, parsed.offset + parsed.limit);
const links = uniqueSorted(
correlations.flatMap(({ evidence_links: evidenceLinks }) => evidenceLinks),
);
@@ -220,14 +213,7 @@ export const correlateStaticAndRuntime = (
unresolved: unresolved.length,
},
correlations: {
items: page,
offset: parsed.offset,
limit: parsed.limit,
total: correlations.length,
next_offset:
parsed.offset + page.length < correlations.length
? parsed.offset + page.length
: null,
items: correlations,
},
evidence_links: links,
limitations: [
@@ -324,15 +310,7 @@ const selectStatic = (
const result = artifactComparisonResultSchema.parse(
evidence.normalized_result,
);
if (
result.changes.offset !== 0 ||
result.changes.next_offset !== null ||
result.changes.items.length !== result.changes.total
)
throw new TypeError(
"Static/runtime correlation requires complete artifact comparison pagination",
);
const matches = result.changes.items.filter(
const matches = result.changes.filter(
({ logical_path: path }) => path === selector.logical_path,
);
const match = matches.at(0);
@@ -366,10 +344,9 @@ const parseStatic = (input: unknown): Evidence => {
const result = artifactComparisonResultSchema.parse(
evidence.normalized_result,
);
assertCompleteArtifactResult(result);
assertNestedLinks(
evidence,
result.changes.items.flatMap(({ evidence_links: links }) => links),
result.changes.flatMap(({ evidence_links: links }) => links),
);
} else {
const result = functionComparisonResultSchema.parse(
@@ -470,19 +447,6 @@ const sameSet = (left: readonly string[], right: readonly string[]): boolean =>
new Set(left).size === left.length &&
left.every((item) => right.includes(item));
const assertCompleteArtifactResult = (
result: z.infer<typeof artifactComparisonResultSchema>,
): void => {
if (
result.changes.offset !== 0 ||
result.changes.next_offset !== null ||
result.changes.items.length !== result.changes.total
)
throw new TypeError(
"Static/runtime correlation requires complete artifact comparison pagination",
);
};
const observedPattern = (
staticStatus: ComparisonStatus,
runtimeStatus: ComparisonStatus,
@@ -369,7 +369,6 @@ export const buildNativeBoundaryInspection = ({
issues,
}: BoundaryInspectionContext): ManagedNativeBoundaryInspection =>
managedNativeBoundaryInspectionSchema.parse({
schema_version: 1,
artifact: {
path: target.path,
sha256: target.sha256,
-6
View File
@@ -89,12 +89,6 @@ export const MANAGED_MEMBER_FIXTURE_LIMITS = {
maxMethodInstructions: 1_000,
};
/** Member bounds for fixture tests that inspect a real local path. */
export const MANAGED_MEMBER_PATH_FIXTURE_LIMITS = {
...MANAGED_MEMBER_FIXTURE_LIMITS,
maxFileBytes: 1024 * 1024,
};
/** Generous native-boundary bounds for the source-owned PE fixture. */
export const MANAGED_NATIVE_BOUNDARY_FIXTURE_LIMITS = {
maxMetadataBytes: 1024 * 1024,
-7
View File
@@ -50,8 +50,6 @@ export class ManagedStaticProvider implements AnalysisProvider {
operation: contract.name,
available: true as const,
reason: null,
pagination: "none" as const,
exhaustive: true,
effects: Object.freeze({
mutatesArtifact: false,
launchesProcess: false,
@@ -61,11 +59,6 @@ export class ManagedStaticProvider implements AnalysisProvider {
changesPermissions: false,
requiresRoot: false,
}),
limits: Object.freeze({
maxResults: null,
maxPayloadBytes: 4 * 1024 * 1024,
timeoutMs: null,
}),
limitations: limitationsFor(contract.name),
}),
),
-2
View File
@@ -95,7 +95,6 @@ export class GhidraHeadlessLauncher implements GhidraLauncher {
await writeFileAtomic(
paths.descriptorPath,
`${JSON.stringify({
schema_version: 2,
transport: session.transport,
endpoint_path: session.endpointPath,
token: session.token,
@@ -137,7 +136,6 @@ export class GhidraHeadlessLauncher implements GhidraLauncher {
await writeFileAtomic(
paths.ownershipPath,
`${JSON.stringify({
schema_version: 1,
run_id: session.runId,
pid: started.ownership.leaderPid,
process_group_id: started.ownership.processGroupId,
@@ -65,26 +65,14 @@ describe("Ghidra provider", () => {
expect.arrayContaining([
expect.objectContaining({
operation: "list_procedures",
pagination: "none",
effects: expect.objectContaining({
mutatesArtifact: false,
mayShowUi: false,
mayWriteFilesystem: true,
}),
limits: {
maxResults: null,
maxPayloadBytes: 1024 * 1024,
timeoutMs: 10_000,
},
}),
expect.objectContaining({
operation: "analyze_function",
pagination: "none",
limits: {
maxResults: null,
maxPayloadBytes: 1024 * 1024,
timeoutMs: 35_000,
},
limitations: expect.arrayContaining([
expect.stringContaining(
"unresolved targetless flows remain unknown",
-1
View File
@@ -39,7 +39,6 @@ describe("Ghidra provider capabilities", () => {
Reflect.set(capabilities, 0, { ...first, available: false });
Reflect.set(first, "available", false);
Reflect.set(first.effects, "mutatesArtifact", true);
Reflect.set(first.limits, "maxResults", 0);
Reflect.set(first.limitations, 0, "forged limitation");
expect(provider.capabilities()).toEqual(published);
-22
View File
@@ -4,11 +4,6 @@ import type {
} from "../application/AnalysisProvider.js";
import { WINDOWS_NATIVE_AUTHORITY_UNAVAILABLE_REASON } from "../process/WindowsAuthority.js";
import { GENERATED_MCP_TOOL_CATALOG } from "../generatedMcpToolCatalog.js";
import {
GHIDRA_DECOMPILE_REQUEST_TIMEOUT_MS,
GHIDRA_MAX_LINE_BYTES,
GHIDRA_REQUEST_TIMEOUT_MS,
} from "./GhidraDefaults.js";
import { GHIDRA_FUNCTION_OPERATIONS } from "./GhidraFunctionValues.js";
import { GHIDRA_INVENTORY_OPERATIONS } from "./GhidraInventoryValues.js";
@@ -39,12 +34,6 @@ export const GHIDRA_PROVIDER_TOOL_CONTRACTS = Object.freeze(
),
);
const PAGINATED_OPERATIONS: ReadonlySet<string> = new Set([]);
const DECOMPILE_OPERATIONS: ReadonlySet<string> = new Set([
"procedure_pseudo_code",
"analyze_function",
]);
/** Health limitations shared by every Ghidra-backed capability. */
export const healthLimitations = Object.freeze([
"The session serves operations only after default Ghidra auto-analysis completes; an analysis timeout fails the open instead of exposing partial results.",
@@ -157,10 +146,6 @@ export const CAPABILITIES: readonly CapabilityDescriptor[] = Object.freeze(
operation,
available: true,
reason: null,
pagination: PAGINATED_OPERATIONS.has(contract.name)
? ("offset" as const)
: ("none" as const),
exhaustive: !PAGINATED_OPERATIONS.has(contract.name),
effects: Object.freeze({
mutatesArtifact: false,
launchesProcess: true,
@@ -170,13 +155,6 @@ export const CAPABILITIES: readonly CapabilityDescriptor[] = Object.freeze(
changesPermissions: false,
requiresRoot: false,
}),
limits: Object.freeze({
maxResults: PAGINATED_OPERATIONS.has(contract.name) ? 500 : null,
maxPayloadBytes: GHIDRA_MAX_LINE_BYTES,
timeoutMs: DECOMPILE_OPERATIONS.has(contract.name)
? GHIDRA_DECOMPILE_REQUEST_TIMEOUT_MS
: GHIDRA_REQUEST_TIMEOUT_MS,
}),
limitations: Object.freeze(limitationsFor(contract.name)),
});
}),
+1 -2
View File
@@ -79,8 +79,7 @@ const parseTcpEndpoint = (
typeof value !== "object" ||
value === null ||
Array.isArray(value) ||
!hasExactKeys(value, ["host", "port", "schema_version"]) ||
value.schema_version !== 1 ||
!hasExactKeys(value, ["host", "port"]) ||
value.host !== "127.0.0.1" ||
!Number.isSafeInteger(value.port) ||
typeof value.port !== "number" ||
-1
View File
@@ -193,7 +193,6 @@ export class HopperApplicationLauncher implements BridgeLauncher {
lease?.release(),
);
const ownership = {
schema_version: 1,
run_id: session.runId,
pid: started.ownership.leaderPid,
process_group_id: started.ownership.leaderPid,
+1 -2
View File
@@ -25,7 +25,7 @@ describe("Hopper provider capabilities", () => {
}
expect(
capabilities.find(({ operation }) => operation === "list_procedures"),
).toMatchObject({ pagination: "none" });
).toMatchObject({ available: true });
expect(
capabilities.find(({ operation }) => operation === "set_comment"),
).toMatchObject({
@@ -36,7 +36,6 @@ describe("Hopper provider capabilities", () => {
Reflect.set(capabilities, 0, { ...first, available: false });
Reflect.set(first, "available", false);
Reflect.set(first.effects, "mutatesArtifact", true);
Reflect.set(first.limits, "maxResults", 0);
Reflect.set(first.limitations, 0, "forged limitation");
expect(provider.capabilities()).toEqual(published);
-15
View File
@@ -60,12 +60,6 @@ const CAPABILITIES: readonly CapabilityDescriptor[] = Object.freeze(
operation,
available: true,
reason: null,
pagination:
isRecord(contract.inputSchema.properties) &&
"offset" in contract.inputSchema.properties
? "offset"
: "none",
exhaustive: false,
effects: Object.freeze({
mutatesArtifact: MUTATING_OPERATIONS.has(contract.name),
launchesProcess: true,
@@ -75,11 +69,6 @@ const CAPABILITIES: readonly CapabilityDescriptor[] = Object.freeze(
changesPermissions: false,
requiresRoot: false,
}),
limits: Object.freeze({
maxResults: null,
maxPayloadBytes: contract.name === "read_bytes" ? 4_096 : null,
timeoutMs: null,
}),
limitations: Object.freeze([
"Results depend on Hopper's completed static analysis.",
]),
@@ -87,10 +76,6 @@ const CAPABILITIES: readonly CapabilityDescriptor[] = Object.freeze(
}),
);
function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
/** Concrete analysis provider backed by REA's private Hopper bridge. */
export class HopperProvider implements AnalysisProviderCandidate {
constructor(
+2 -8
View File
@@ -45,24 +45,18 @@ describe("Hopper provider discovery", () => {
});
});
it("advertises the exact bounded byte-read limit", () => {
it("advertises byte reads as an available operation", () => {
const capabilities = provider(process.execPath).capabilities();
expect(
capabilities.find(({ operation }) => operation === "read_bytes"),
).toMatchObject({
available: true,
pagination: "none",
limits: {
maxResults: null,
maxPayloadBytes: 4_096,
timeoutMs: null,
},
});
expect(
capabilities.find(
({ operation }) => operation === "address_to_file_offset",
),
).toMatchObject({ available: true, pagination: "none" });
).toMatchObject({ available: true });
});
});
@@ -8,7 +8,6 @@ import {
export const LINUX_PRIVATE_DISPLAY_DIAGNOSTIC_PREFIX = "REA_X11_DIAGNOSTIC_V1=";
const diagnosticContextSchema = z.object({
schema_version: z.literal(1),
component: z.literal("hopper_private_display"),
operation: z.enum(["probe", "launch"]),
reason: z
-1
View File
@@ -267,7 +267,6 @@ const syntheticDiagnostic = (
reason: string,
failureCode: HopperStartupFailureCode = "private_display_unavailable",
): HopperStartupFailureDiagnostic => ({
schema_version: 1,
component: "hopper_private_display",
operation: "probe",
status: "error",
-7
View File
@@ -82,8 +82,6 @@ export class NativeMacOSProvider implements AnalysisProvider {
provider: IDENTITY,
operation: contract.name,
...availability,
pagination: "none" as const,
exhaustive: contract.name !== "inspect_macho",
effects: Object.freeze({
mutatesArtifact: false,
launchesProcess: true,
@@ -93,11 +91,6 @@ export class NativeMacOSProvider implements AnalysisProvider {
changesPermissions: false,
requiresRoot: false,
}),
limits: Object.freeze({
maxResults: contract.name === "demangle_swift" ? 500 : null,
maxPayloadBytes: OUTPUT_LIMIT,
timeoutMs: TIMEOUT_MS,
}),
limitations: Object.freeze([
"Availability and textual formats depend on the installed macOS/Xcode toolchain.",
]),
-1
View File
@@ -126,7 +126,6 @@ const main = async (): Promise<void> => {
}
process.stdout.write(
JSON.stringify({
schema_version: 1,
left,
...(request.right === undefined ? {} : { right }),
}),
+3 -14
View File
@@ -16,15 +16,11 @@ const outcome = {
describe("replay worker protocol", () => {
it("accepts only the exact committed case sequence", () => {
expect(
parseReplayWorkerResponse(
{ schema_version: 1, left: [outcome] },
cases,
"single",
),
parseReplayWorkerResponse({ left: [outcome] }, cases, "single"),
).toMatchObject({ left: [outcome] });
expect(() =>
parseReplayWorkerResponse(
{ schema_version: 1, left: [{ ...outcome, case_id: "forged" }] },
{ left: [{ ...outcome, case_id: "forged" }] },
cases,
"single",
),
@@ -32,7 +28,6 @@ describe("replay worker protocol", () => {
expect(() =>
parseReplayWorkerResponse(
{
schema_version: 1,
left: [{ ...outcome, input_sha256: "2".repeat(64) }],
},
cases,
@@ -43,16 +38,11 @@ describe("replay worker protocol", () => {
it("requires the exact differential shape and strict outcomes", () => {
expect(() =>
parseReplayWorkerResponse(
{ schema_version: 1, left: [outcome] },
cases,
"differential",
),
parseReplayWorkerResponse({ left: [outcome] }, cases, "differential"),
).toThrow();
expect(() =>
parseReplayWorkerResponse(
{
schema_version: 1,
left: [{ ...outcome, unexpected: true }],
},
cases,
@@ -62,7 +52,6 @@ describe("replay worker protocol", () => {
expect(() =>
parseReplayWorkerResponse(
{
schema_version: 1,
left: [{ ...outcome, outcome: "exception", value: undefined }],
},
cases,
-2
View File
@@ -30,14 +30,12 @@ const workerOutcomeSchema = z.discriminatedUnion("outcome", [
const singleWorkerResponseSchema = z
.object({
schema_version: z.literal(1),
left: z.array(workerOutcomeSchema),
})
.strict();
const differentialWorkerResponseSchema = z
.object({
schema_version: z.literal(1),
left: z.array(workerOutcomeSchema),
right: z.array(workerOutcomeSchema),
})
+1 -1
View File
@@ -151,7 +151,7 @@ const evidenceValues = (
);
if (!inventory.success) return [];
if (kind === "manifest") return [inventory.data.manifest.manifest_id];
return inventory.data.occurrences.items.map(({ occurrence_id: id }) => id);
return inventory.data.occurrences.map(({ occurrence_id: id }) => id);
};
const isProcessCaptureEvidence = (evidence: LedgerEvidence): boolean => {
@@ -58,8 +58,8 @@ const registerCommitTool = (
options.permissionAuthority,
{
path: input.workspace_path,
readCapability: "investigation_workspace_read",
writeCapability: "investigation_workspace_write",
readCapability: "reconstruction_coverage_read",
writeCapability: "reconstruction_coverage_write",
operation: commitContract.name,
},
);
@@ -104,7 +104,7 @@ const registerQueryTool = (
const authorized = await authorizeRootPermission(
options.permissionAuthority,
{
capability: "investigation_workspace_read",
capability: "reconstruction_coverage_read",
roots: [input.workspace_path],
access: "read",
operation: queryContract.name,
+9 -11
View File
@@ -29,31 +29,29 @@ export const registerArtifactComparisonTool = (
};
const left = resolveSessionEvidenceIds(
session,
input.left_evidence_ids,
[input.left_evidence_id],
expected,
);
if (!left.ok) return toCallToolResult(left, contract);
const right = resolveSessionEvidenceIds(
session,
input.right_evidence_ids,
[input.right_evidence_id],
expected,
);
if (!right.ok) return toCallToolResult(right, contract);
const computed = await runDerivedOperation(context, contract.name, () =>
compareArtifacts(left.value, right.value, input.offset, input.limit),
compareArtifacts(left.value, right.value),
);
if (!computed.ok) return toCallToolResult(computed, contract);
const comparison = computed.value;
const leftEvidenceIds = input.left_evidence_ids;
const rightEvidenceIds = input.right_evidence_ids;
const leftEvidenceIds = [input.left_evidence_id];
const rightEvidenceIds = [input.right_evidence_id];
const evidence = createEvidence(undefined, ARTIFACT_COMPARISON_PROVIDER, {
predicateType: "rea.artifact-comparison/v1",
operation: contract.name,
parameters: {
left_evidence_ids: leftEvidenceIds,
right_evidence_ids: rightEvidenceIds,
offset: input.offset,
limit: input.limit,
},
result: jsonValueSchema.parse(comparison),
confidence: "derived",
@@ -75,8 +73,8 @@ export const registerArtifactComparisonTool = (
const artifactUnknownInput = (
input: {
readonly left_evidence_ids: readonly string[];
readonly right_evidence_ids: readonly string[];
readonly left_evidence_id: string;
readonly right_evidence_id: string;
readonly unknown_registry_approved?: true | undefined;
},
status: ReturnType<typeof compareArtifacts>["status"],
@@ -89,8 +87,8 @@ const artifactUnknownInput = (
severity:
status === "unknown" || status === "truncated" ? "high" : "medium",
domain: "artifact-comparison",
supporting_evidence_ids: [...input.left_evidence_ids],
contradicting_evidence_ids: [...input.right_evidence_ids],
supporting_evidence_ids: [input.left_evidence_id],
contradicting_evidence_ids: [input.right_evidence_id],
required_authority: "shipped-artifact",
required_confidence: "observed",
required_environment: null,
+1 -9
View File
@@ -31,13 +31,7 @@ export const registerBundleComparisonTool = (
if (!left.ok) return toCallToolResult(left, contract);
if (!right.ok) return toCallToolResult(right, contract);
const computed = await runDerivedOperation(context, contract.name, () =>
compareBundles(
left.value,
right.value,
input.record_pairs,
input.offset,
input.limit,
),
compareBundles(left.value, right.value, input.record_pairs),
);
if (!computed.ok) return toCallToolResult(computed, contract);
const comparison = computed.value;
@@ -48,8 +42,6 @@ export const registerBundleComparisonTool = (
left_bundle_sha256: comparison.left_bundle_sha256,
right_bundle_sha256: comparison.right_bundle_sha256,
record_pairs: input.record_pairs,
offset: input.offset,
limit: input.limit,
},
result: jsonValueSchema.parse(comparison),
confidence: "derived",
+10 -2
View File
@@ -17,6 +17,7 @@ import { mcpProgressReporter } from "./mcpProgress.js";
import { logToolExecution } from "./toolLogging.js";
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
import { toCallToolResult } from "./toolResult.js";
import { createArtifactExtractionDestination } from "../application/ArtifactExtractionDestination.js";
interface EvidenceToolRegistration {
readonly logger: Logger;
@@ -49,8 +50,15 @@ export const registerEvidenceTools = (
message: "started",
});
const parameters = jsonObjectSchema.parse(input);
const executionParameters =
contract.name === "extract_artifact"
? {
...parameters,
output_root: createArtifactExtractionDestination(),
}
: parameters;
if (options.permissionAuthority !== undefined) {
const request = permissionRequest(contract.name, parameters);
const request = permissionRequest(contract.name, executionParameters);
if (request !== undefined) {
const authorized = await options.permissionAuthority.authorize(
request,
@@ -67,7 +75,7 @@ export const registerEvidenceTools = (
options.logger,
contract.name,
() =>
analysis.execute(contract.name, parameters, {
analysis.execute(contract.name, executionParameters, {
signal: context.mcpReq.signal,
progress,
}),
-22
View File
@@ -97,25 +97,3 @@ export const createServerIdentity = (input: {
},
} as const;
};
/** Compact identity projection for CLI summaries; MCP retains full identity. */
export const createServerIdentitySummary = (
input: Parameters<typeof createServerIdentity>[0],
) => {
const identity = createServerIdentity(input);
return {
package: {
name: identity.package.name,
version: identity.package.version,
},
server: {
name: identity.server.name,
version: identity.server.version,
},
catalog: {
counts: identity.catalog.counts,
digests: identity.catalog.digests,
},
alignment: identity.alignment,
} as const;
};
@@ -78,7 +78,6 @@ async function runInlineEvidenceScenarios(
expect(compared.isError).not.toBe(true);
expect(compared.structuredContent).toMatchObject({
result: {
schema_version: 1,
summary: { unknown: expect.any(Number) },
coverage: { status: expect.any(String) },
},
@@ -94,7 +93,6 @@ async function runInlineEvidenceScenarios(
expect(sourceCompared.isError).not.toBe(true);
expect(sourceCompared.structuredContent).toMatchObject({
result: {
schema_version: 1,
reference: {
root_sha256: SOURCE_TO_BUNDLE_COMPARISON_EXAMPLE.reference.root_sha256,
},
@@ -116,7 +116,6 @@ describe("application workflow MCP parity", () => {
expect(semantic.isError).not.toBe(true);
expect(semantic.structuredContent).toMatchObject({
result: {
schema_version: 1,
source_evidence_id: left.value.evidence_id,
source_graph_id: analyzed.semantic_graph.graph_id,
summary: { retained_seed_matches: 1 },
@@ -93,7 +93,6 @@ function createReplayScenario(root: string) {
},
javascriptReplayRunner: {
execute: async (prepared) => ({
schema_version: 1,
plan_digest: prepared.publicPlan.plan_digest,
outcomes: [
{
@@ -60,7 +60,6 @@ describe("application workflow CLI parity", () => {
expect(compared).toMatchObject({
operation: "compare_application_versions",
normalized_result: {
schema_version: 1,
summary: { unknown: expect.any(Number) },
},
});
@@ -74,7 +73,7 @@ describe("application workflow CLI parity", () => {
]);
expect(sourceCompared).toMatchObject({
operation: "compare_source_to_bundle",
normalized_result: { schema_version: 1 },
normalized_result: { reference: expect.any(Object) },
});
}, 20_000);
@@ -177,7 +176,7 @@ describe("application workflow CLI Evidence resolution", () => {
);
expect(compared).toMatchObject({
operation: "compare_application_versions",
normalized_result: { schema_version: 1 },
normalized_result: { summary: expect.any(Object) },
});
const missingRecord = await runCli(
@@ -322,7 +321,6 @@ describe("application workflow CLI validation and replay", () => {
"--json",
]);
expect(inline).toMatchObject({
schema_version: 1,
final_state: "complete",
decisions: [{ outcome: "matched" }],
transition_journal: [
-56
View File
@@ -13,7 +13,6 @@ import {
} from "../../../src/cliOutput.js";
const CLI_INTEGRATION_TIMEOUT_MS = 60_000;
const CLI_VARIANT_TIMEOUT_MS = 120_000;
describe("CLI output argument and sanitization boundary", () => {
it("rejects token windows that would corrupt structured output", () => {
@@ -175,59 +174,4 @@ describe("compiled CLI output boundary", () => {
},
CLI_INTEGRATION_TIMEOUT_MS,
);
workspaceCliTest(
"keeps operation failure status independent of output controls",
async ({ cli }) => {
const variants = [
["--format", "toon"],
["--format", "json"],
["--format", "yaml"],
["--format", "md"],
["--format", "jsonl"],
["--filter-output", "category", "--json"],
["--token-limit", "5", "--json"],
["--token-count", "--json"],
];
for (const flags of variants) {
const result = await cli.run({
arguments: [
...flags,
"investigate-versions",
"/tmp/left",
"/tmp/right",
"/tmp/workspace.json",
],
});
expect(result).toMatchObject({
exitCode: 1,
stdout: expect.any(String),
});
}
},
CLI_VARIANT_TIMEOUT_MS,
);
workspaceCliTest(
"keeps failure logs out of structured stdout",
async ({ cli }) => {
const result = await cli.run({
arguments: [
"--json",
"investigate-versions",
"/tmp/left",
"/tmp/right",
"/tmp/workspace.json",
],
environment: { REA_LOG_LEVEL: "error" },
});
expect(result).toMatchObject({
exitCode: 1,
stderr: expect.stringContaining('"status":"error"'),
});
expect(result.json).toMatchObject({ error: "ApprovalRequired" });
},
CLI_INTEGRATION_TIMEOUT_MS,
);
});
@@ -16,7 +16,6 @@ describe("reconstruction obligation ledger CLI parity", () => {
bindings: [],
contradictions: [],
},
page: { offset: 0, limit: 50 },
};
const { stdout } = await execute(
process.execPath,

Some files were not shown because too many files have changed in this diff Show More