@axtary/cli 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -8,10 +8,11 @@ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
8
8
  import { ACTIONPASS_V2_VERSION, ACTIONPASS_V1_VERSION, analyzePostgresSelect, AxtaryDecisionSchema, FileDpopReplayStore, LocalActionPassStatusStore, LocalCaepMappingStore, LocalSsfReplayStore, LocalActionPassTrustStore, createApprovalArtifact, demoAction, hashPayload, issuerJwks, issuerSigningKey, issuerVerificationKeyResolver, explainDecision, loadOrCreateIssuerKeyring, parseNormalizedAction, processCaepSessionRevoked, rotateIssuerKeyring, signStatusListToken, } from "@axtary/actionpass";
9
9
  import { createFakeAdapterState, createFakeHandlers, buildNativeConnectorDoctorPlans, createAwsRestHandlers, createActionPassVerifiedHandler, createGcpRestHandlers, createGitHubRestHandlers, createJiraRestHandlers, createLinearGraphqlHandlers, createLocalDocsHandlers, createPostgresNativeHandlers, createDriveRestHandlers, createSlackWebHandlers, GITHUB_NATIVE_CONNECTOR, githubAppRequiredEnv, mintGithubInstallationToken, resolveGithubAppCredentials, smokeAwsRestCredentials, smokeGcpRestCredentials, smokeGithubAppCredentials, smokeGitHubRestCredentials, smokeJiraRestCredentials, smokeLinearGraphqlCredentials, smokeLocalDocsRoots, smokePostgresCredentials, smokeDriveRestCredentials, smokeSlackWebCredentials, } from "@axtary/adapters";
10
10
  import { loadAxtaryConfig, CachedConfigLoader } from "@axtary/config";
11
- import { analyzeForensics, attestLedgerExport, buildLedgerAttestationBundle, exportLedgerRecords, formatLedgerExport, LedgerAttestationBundleSchema, LedgerExportFormatSchema, LocalJsonlLedger, OtlpHttpTraceExporter, proveLedgerConsistency, proveLedgerInclusion, syncLedgerExport, exportLedgerRecordsToOtlp, verifyLedgerAttestationBundle, verifyLedgerConsistencyProof, verifyLedgerInclusionProof, } from "@axtary/ledger";
11
+ import { analyzeForensics, attestLedgerExport, buildLedgerAttestationBundle, CROSS_ISSUER_TRUST_ROOT_VERSION, evaluateLedgerEquivalence, exportLedgerRecords, formatLedgerExport, LedgerAttestationBundleSchema, LedgerExportFormatSchema, LocalJsonlLedger, OtlpHttpTraceExporter, proveLedgerConsistency, proveLedgerInclusion, syncLedgerExport, exportLedgerRecordsToOtlp, verifyLedgerAttestationBundle, verifyLedgerConsistencyProof, verifyLedgerInclusionProof, } from "@axtary/ledger";
12
12
  import { MCP_TOOL_CALL, McpHttpClient, McpStdioClient, createMcpAction, createMcpFixtureToolHandler, createMcpToolDefinition, createMcpToolRouter, createMcpWrapperServer, createMcpWrapperTool, evaluateMcpVersionChain, findMcpSignedDefinition, findMcpToolPin, loadMcpConformanceRegistry, loadMcpPinManifest, loadMcpPublisherTrustStore, loadMcpSignedDefinitionRegistry, McpPublisherKeySchema, mcpPublisherKeyResolver, pinMcpDefinitions, reconcileMcpPins, saveMcpConformanceRegistry, saveMcpPinManifest, saveMcpPublisherTrustStore, trustMcpPublisher, upsertMcpConformanceReceipt, verifyMcpSignedDefinition, } from "@axtary/mcp";
13
13
  import { createPollingHostedApprovalResolver } from "@axtary/approvals";
14
14
  import { applyPolicyTemplate, evaluatePolicy, explainPolicyDecision, getPolicyTemplate, getPolicyTemplates, runPolicyFixtures as runPolicyTemplateFixtures, simulatePolicy, } from "@axtary/policy";
15
+ import { actionFromAcsToolCallSnapshot, createAxtaryAcsManifest, evaluateAcsIntervention, } from "@axtary/policy/acs";
15
16
  import { createProxyRuntime, } from "@axtary/proxy";
16
17
  import { runClaudeCodeHook } from "./claude-code-hook.js";
17
18
  import { runCodexHook } from "./codex-hook.js";
@@ -37,6 +38,7 @@ export const LEDGER_OTEL_EXPORT_RUN_SCHEMA_VERSION = "axtary.ledger_otel_export_
37
38
  export const LEDGER_ATTEST_RUN_SCHEMA_VERSION = "axtary.ledger_attest_run.v0";
38
39
  export const LEDGER_VERIFY_EXPORT_RUN_SCHEMA_VERSION = "axtary.ledger_verify_export_run.v0";
39
40
  export const LEDGER_PROVE_INCLUSION_RUN_SCHEMA_VERSION = "axtary.ledger_prove_inclusion_run.v0";
41
+ export const LEDGER_PROVE_EQUIVALENCE_RUN_SCHEMA_VERSION = "axtary.ledger_prove_equivalence_run.v0";
40
42
  export const LEDGER_PROVE_CONSISTENCY_RUN_SCHEMA_VERSION = "axtary.ledger_prove_consistency_run.v0";
41
43
  export const LEDGER_VERIFY_PROOF_RUN_SCHEMA_VERSION = "axtary.ledger_verify_proof_run.v0";
42
44
  export const LEDGER_FORENSICS_RUN_SCHEMA_VERSION = "axtary.ledger_forensics_run.v0";
@@ -95,6 +97,7 @@ export function authorizeDecisionHints(decision, policy) {
95
97
  return hints;
96
98
  }
97
99
  export const POLICY_PARITY_SCHEMA_VERSION = "axtary.policy_parity.v0";
100
+ export const ACS_CONFORMANCE_SCHEMA_VERSION = "axtary.acs_conformance.v0";
98
101
  const MCP_DEMO_TOOL_DEFINITION = createMcpToolDefinition({
99
102
  serverIdentity: "mcp:axtary-demo-fixtures",
100
103
  serverVersion: "1.0.0",
@@ -295,6 +298,9 @@ export async function startProxyServer(input = {}) {
295
298
  endpoint: input.otlpEndpoint,
296
299
  serviceName: input.otlpServiceName ?? "axtary-local-proxy",
297
300
  timeoutMs: input.otlpTimeoutMs,
301
+ // The ledger library no longer falls back to ambient fetch; the
302
+ // CLI is the app boundary that supplies the transport.
303
+ fetch: globalThis.fetch,
298
304
  }),
299
305
  }
300
306
  : {});
@@ -375,6 +381,20 @@ export async function startProxyServer(input = {}) {
375
381
  writeJson(response, 200, issuerJwks(keyring));
376
382
  return;
377
383
  }
384
+ if (request.method === "GET" &&
385
+ request.url === "/.well-known/axtary-trust-root.json") {
386
+ writeJson(response, 200, {
387
+ schemaVersion: CROSS_ISSUER_TRUST_ROOT_VERSION,
388
+ issuer: loadedConfig.config.issuer,
389
+ jwks: issuerJwks(keyring),
390
+ statusListUri,
391
+ federationProfile: {
392
+ profile: "axtary.openid-federation-anchor.v0",
393
+ entityStatementTyp: "entity-statement+jwt",
394
+ },
395
+ });
396
+ return;
397
+ }
378
398
  if (request.method === "GET" &&
379
399
  request.url === "/statuslists/actionpasses") {
380
400
  const signed = await signStatusListToken({
@@ -1000,7 +1020,11 @@ async function connectMcpUpstream(target) {
1000
1020
  });
1001
1021
  headers = { ...headers, authorization };
1002
1022
  }
1003
- client = await McpHttpClient.start({ url: target.wrapUrl, headers });
1023
+ client = await McpHttpClient.start({
1024
+ url: target.wrapUrl,
1025
+ headers,
1026
+ fetch: globalThis.fetch,
1027
+ });
1004
1028
  }
1005
1029
  else if (target.wrap) {
1006
1030
  const [command, ...commandArgs] = target.wrap.split(/\s+/).filter(Boolean);
@@ -1899,6 +1923,17 @@ export async function runDemo(input = {}) {
1899
1923
  verificationKey: publicKey,
1900
1924
  }),
1901
1925
  },
1926
+ // Same exact-payload approval primitive as the real workflows: the
1927
+ // artifact binds the payload hash, and the proxy stamps the
1928
+ // approved/executed hash pair on the execution record.
1929
+ approvalResolver: input.approveStepUp
1930
+ ? async ({ action }) => createApprovalArtifact({
1931
+ action,
1932
+ mode: "human",
1933
+ approvedBy: input.approvedBy ?? "user:local-reviewer",
1934
+ reason: "Local exact approval for the demo step-up action.",
1935
+ }).artifact
1936
+ : undefined,
1902
1937
  });
1903
1938
  const results = [];
1904
1939
  for (const demo of DEMO_ACTIONS) {
@@ -2591,18 +2626,172 @@ export async function runPolicyParity(input = {}) {
2591
2626
  results.push({ id: fixture.id, passed, expected, engines });
2592
2627
  }
2593
2628
  const failed = results.filter((result) => !result.passed).length;
2629
+ const agentCore = parity.buildAgentCoreCedarExport({
2630
+ fixtures: fixturesFile.fixtures.map((fixture) => ({
2631
+ id: fixture.id,
2632
+ action: fixture.action,
2633
+ })),
2634
+ policyVersion: policyConfig.version,
2635
+ });
2594
2636
  return {
2595
2637
  schemaVersion: POLICY_PARITY_SCHEMA_VERSION,
2596
2638
  engines: ["native", "cedar", "rego"],
2597
2639
  fixturesPath,
2598
2640
  cedarPath,
2599
2641
  regoWasmPath,
2642
+ agentCore: {
2643
+ schemaVersion: agentCore.schemaVersion,
2644
+ status: agentCore.status,
2645
+ gatewayResource: agentCore.gatewayResource,
2646
+ fixtureCount: agentCore.fixtures.length,
2647
+ sample: (agentCore.fixtures[0] ?? null),
2648
+ },
2600
2649
  passed: failed === 0,
2601
2650
  total: results.length,
2602
2651
  failed,
2603
2652
  results,
2604
2653
  };
2605
2654
  }
2655
+ export async function runAcsConformance(input = {}) {
2656
+ const loadedConfig = await loadAxtaryConfig({
2657
+ filePath: input.configPath,
2658
+ cwd: input.cwd,
2659
+ });
2660
+ const cwd = resolve(input.cwd ?? process.cwd());
2661
+ const ledgerPath = input.ledgerPath
2662
+ ? resolve(cwd, input.ledgerPath)
2663
+ : resolve(cwd, ".axtary/acs-conformance.jsonl");
2664
+ const ledger = new LocalJsonlLedger(ledgerPath);
2665
+ const { privateKey } = generateKeyPairSync("ec", { namedCurve: "P-256" });
2666
+ const manifest = createAxtaryAcsManifest();
2667
+ const runtime = createProxyRuntime({
2668
+ issuer: loadedConfig.config.issuer,
2669
+ tenant: loadedConfig.config.tenant,
2670
+ ledger,
2671
+ policy: loadedConfig.config.policy,
2672
+ signingKey: privateKey,
2673
+ keyId: "local-acs-conformance-key",
2674
+ allowUnsignedExecution: false,
2675
+ handlerTimeoutMs: loadedConfig.config.runtime.handlerTimeoutMs,
2676
+ budget: loadedConfig.config.runtime.budget,
2677
+ handlers: {
2678
+ "slack.chat.postMessage": async () => ({
2679
+ ok: true,
2680
+ provider: "acs-fixture",
2681
+ }),
2682
+ },
2683
+ });
2684
+ const vectors = [
2685
+ {
2686
+ id: "pre-tool-call-allow",
2687
+ interventionPoint: "pre_tool_call",
2688
+ snapshot: acsSlackSnapshot({
2689
+ channel: "#axtary-dev",
2690
+ message: "M9.4 ACS conformance allow",
2691
+ }),
2692
+ expectedVerdict: "allow",
2693
+ expectedProxyStatus: "executed",
2694
+ mode: "handle",
2695
+ },
2696
+ {
2697
+ id: "pre-tool-call-deny",
2698
+ interventionPoint: "pre_tool_call",
2699
+ snapshot: acsSlackSnapshot({
2700
+ channel: "#not-allowed",
2701
+ message: "M9.4 ACS conformance deny",
2702
+ }),
2703
+ expectedVerdict: "deny",
2704
+ expectedProxyStatus: "blocked",
2705
+ mode: "handle",
2706
+ },
2707
+ {
2708
+ id: "pre-tool-call-step-up",
2709
+ interventionPoint: "pre_tool_call",
2710
+ snapshot: acsSlackSnapshot({
2711
+ channel: "#axtary-dev",
2712
+ message: "M9.4 ACS conformance step-up",
2713
+ externalRecipients: ["customer@example.com"],
2714
+ }),
2715
+ expectedVerdict: "escalate",
2716
+ expectedProxyStatus: "step_up",
2717
+ mode: "authorize",
2718
+ },
2719
+ ];
2720
+ const results = [];
2721
+ for (const vector of vectors) {
2722
+ const acs = evaluateAcsIntervention({
2723
+ manifest,
2724
+ interventionPoint: vector.interventionPoint,
2725
+ snapshot: vector.snapshot,
2726
+ config: loadedConfig.config.policy,
2727
+ });
2728
+ const action = actionFromAcsToolCallSnapshot(vector.snapshot);
2729
+ const proxy = vector.mode === "authorize"
2730
+ ? await runtime.authorize(action)
2731
+ : await runtime.handle(action);
2732
+ const proxyStatus = "status" in proxy ? proxy.status : null;
2733
+ const ledgerLine = "ledger" in proxy ? proxy.ledger.lineNumber : null;
2734
+ const executionLedgerLine = "executionLedger" in proxy ? proxy.executionLedger.lineNumber : null;
2735
+ const actionPassId = "actionPass" in proxy && proxy.actionPass
2736
+ ? proxy.actionPass.claims.jti
2737
+ : null;
2738
+ const passed = acs.verdict === vector.expectedVerdict &&
2739
+ proxyStatus === vector.expectedProxyStatus &&
2740
+ (vector.expectedProxyStatus !== "executed" || actionPassId !== null) &&
2741
+ ledgerLine !== null;
2742
+ results.push({
2743
+ id: vector.id,
2744
+ interventionPoint: vector.interventionPoint,
2745
+ expectedVerdict: vector.expectedVerdict,
2746
+ actualVerdict: acs.verdict,
2747
+ expectedProxyStatus: vector.expectedProxyStatus,
2748
+ actualProxyStatus: proxyStatus,
2749
+ passed,
2750
+ reasons: acs.reasons,
2751
+ actionHash: acs.evidence.actionHash ?? null,
2752
+ payloadHash: acs.evidence.payloadHash ?? null,
2753
+ actionPassId,
2754
+ ledgerLine,
2755
+ executionLedgerLine,
2756
+ });
2757
+ }
2758
+ const failed = results.filter((result) => !result.passed).length;
2759
+ return {
2760
+ schemaVersion: ACS_CONFORMANCE_SCHEMA_VERSION,
2761
+ manifestVersion: manifest.agent_control_specification_version,
2762
+ ledgerPath,
2763
+ passed: failed === 0,
2764
+ total: results.length,
2765
+ failed,
2766
+ vectors: results,
2767
+ };
2768
+ }
2769
+ function acsSlackSnapshot(input) {
2770
+ return {
2771
+ schemaVersion: "axtary.acs_snapshot.v0",
2772
+ actor: {
2773
+ agentId: "agent:codex-prod",
2774
+ humanOwner: "user:asrar@company.com",
2775
+ runtime: "codex-cli",
2776
+ tenant: "org:axtary",
2777
+ },
2778
+ intent: {
2779
+ taskId: "AXT-M9-4",
2780
+ declaredGoal: "Verify ACS pre_tool_call routing through Axtary",
2781
+ },
2782
+ tool_call: {
2783
+ tool: "slack.chat.postMessage",
2784
+ resource: "slack:workspace/company",
2785
+ arguments: {
2786
+ channel: input.channel,
2787
+ message: input.message,
2788
+ ...(input.externalRecipients
2789
+ ? { externalRecipients: input.externalRecipients }
2790
+ : {}),
2791
+ },
2792
+ },
2793
+ };
2794
+ }
2606
2795
  export async function runLedgerExport(input = {}) {
2607
2796
  const loadedConfig = await loadAxtaryConfig({
2608
2797
  filePath: input.configPath,
@@ -2660,7 +2849,7 @@ export async function runLedgerSync(input) {
2660
2849
  tenant: input.tenant ?? loadedConfig.config.tenant,
2661
2850
  token,
2662
2851
  export: exported,
2663
- fetch: input.fetch,
2852
+ fetch: input.fetch ?? globalThis.fetch,
2664
2853
  });
2665
2854
  return {
2666
2855
  schemaVersion: LEDGER_SYNC_RUN_SCHEMA_VERSION,
@@ -2690,7 +2879,7 @@ export async function runLedgerOtelExport(input) {
2690
2879
  serviceName: input.serviceName ?? "axtary-ledger-export",
2691
2880
  timeoutMs: input.timeoutMs,
2692
2881
  records: exported.records,
2693
- fetch: input.fetch,
2882
+ fetch: input.fetch ?? globalThis.fetch,
2694
2883
  });
2695
2884
  return {
2696
2885
  schemaVersion: LEDGER_OTEL_EXPORT_RUN_SCHEMA_VERSION,
@@ -2819,13 +3008,56 @@ export async function runLedgerVerifyExport(input) {
2819
3008
  const cwd = input.cwd ?? process.cwd();
2820
3009
  const bundlePath = resolve(cwd, input.bundlePath);
2821
3010
  const text = await readFile(bundlePath, "utf8");
2822
- const result = await verifyLedgerAttestationBundle(JSON.parse(text), {
3011
+ const parsedBundle = JSON.parse(text);
3012
+ const result = await verifyLedgerAttestationBundle(parsedBundle, {
2823
3013
  issuer: input.issuer,
2824
3014
  });
3015
+ const equivalence = result.valid
3016
+ ? evaluateLedgerEquivalence(LedgerAttestationBundleSchema.parse(parsedBundle).export.records)
3017
+ : null;
2825
3018
  return {
2826
3019
  schemaVersion: LEDGER_VERIFY_EXPORT_RUN_SCHEMA_VERSION,
2827
3020
  bundlePath,
2828
3021
  result,
3022
+ equivalence,
3023
+ };
3024
+ }
3025
+ /**
3026
+ * `axtary prove-equivalence` — the queryable, exportable approval↔execution
3027
+ * equivalence proof. Reads the ledger through the fail-closed export path
3028
+ * (a tampered chain refuses to export at all), then reports, per execution
3029
+ * record, the approved payload hash, the executed payload hash, and whether
3030
+ * they are equal. Executions without approval evidence report `unproven`,
3031
+ * never fake-proved.
3032
+ */
3033
+ export async function runLedgerProveEquivalence(input = {}) {
3034
+ const loadedConfig = await loadAxtaryConfig({
3035
+ filePath: input.configPath,
3036
+ cwd: input.cwd,
3037
+ });
3038
+ const cwd = input.cwd ?? process.cwd();
3039
+ const ledgerPath = resolveConfiguredLedgerPath(input, loadedConfig);
3040
+ const exported = await exportLedgerRecords({
3041
+ filePath: ledgerPath,
3042
+ from: input.from,
3043
+ to: input.to,
3044
+ decisions: input.decisions,
3045
+ });
3046
+ const report = evaluateLedgerEquivalence(exported.records);
3047
+ const outputPath = input.outputPath ? resolve(cwd, input.outputPath) : null;
3048
+ if (outputPath) {
3049
+ await mkdir(dirname(outputPath), { recursive: true });
3050
+ await writeFile(outputPath, `${JSON.stringify(report, null, 2)}\n`, "utf8");
3051
+ }
3052
+ return {
3053
+ schemaVersion: LEDGER_PROVE_EQUIVALENCE_RUN_SCHEMA_VERSION,
3054
+ config: {
3055
+ filePath: loadedConfig.filePath,
3056
+ tenant: loadedConfig.config.tenant,
3057
+ },
3058
+ ledgerPath,
3059
+ outputPath,
3060
+ report,
2829
3061
  };
2830
3062
  }
2831
3063
  async function readAttestationBundle(cwd, bundlePath) {
@@ -3374,6 +3606,7 @@ export async function runRealWorkflow(input = {}) {
3374
3606
  mutation: tamperSetup.mutation,
3375
3607
  approvedPayloadHash: tamperSetup.approvedPayloadHash,
3376
3608
  attemptedPayloadHash: tamperSetup.attemptedPayloadHash,
3609
+ scopedAuthority: tamperSetup.scopedAuthority,
3377
3610
  reached: tamperStep !== null,
3378
3611
  blocked: tamperStep?.status === "blocked",
3379
3612
  blockReasons: tamperStep?.reasons ?? [],
@@ -4046,6 +4279,8 @@ export async function runAxtaryCli(argv = process.argv.slice(2), io = {}) {
4046
4279
  configPath: parsed.configPath,
4047
4280
  ledgerPath: parsed.ledgerPath,
4048
4281
  cwd: io.cwd ?? parsed.cwd,
4282
+ approveStepUp: parsed.approveStepUp,
4283
+ approvedBy: parsed.approvedBy,
4049
4284
  });
4050
4285
  if (parsed.json) {
4051
4286
  stdout(`${JSON.stringify(result, null, 2)}\n`);
@@ -4230,6 +4465,25 @@ export async function runAxtaryCli(argv = process.argv.slice(2), io = {}) {
4230
4465
  }
4231
4466
  return result.result.valid ? 0 : 1;
4232
4467
  }
4468
+ if (command === "prove-equivalence") {
4469
+ const parsed = parseProveEquivalenceArgs(args);
4470
+ const result = await runLedgerProveEquivalence({
4471
+ configPath: parsed.configPath,
4472
+ ledgerPath: parsed.ledgerPath,
4473
+ cwd: io.cwd ?? parsed.cwd,
4474
+ from: parsed.from,
4475
+ to: parsed.to,
4476
+ decisions: parsed.decisions,
4477
+ outputPath: parsed.outputPath,
4478
+ });
4479
+ if (parsed.json) {
4480
+ stdout(`${JSON.stringify(result, null, 2)}\n`);
4481
+ }
4482
+ else {
4483
+ stdout(formatProveEquivalenceResult(result));
4484
+ }
4485
+ return result.report.failed === 0 ? 0 : 1;
4486
+ }
4233
4487
  if (command === "prove-inclusion") {
4234
4488
  const parsed = parseProveInclusionArgs(args);
4235
4489
  const result = await runLedgerProveInclusion({
@@ -4711,6 +4965,21 @@ export async function runAxtaryCli(argv = process.argv.slice(2), io = {}) {
4711
4965
  }
4712
4966
  return result.passed ? 0 : 1;
4713
4967
  }
4968
+ if (command === "acs") {
4969
+ const parsed = parseAcsArgs(args);
4970
+ const result = await runAcsConformance({
4971
+ configPath: parsed.configPath,
4972
+ ledgerPath: parsed.ledgerPath,
4973
+ cwd: io.cwd ?? parsed.cwd,
4974
+ });
4975
+ if (parsed.json) {
4976
+ stdout(`${JSON.stringify(result, null, 2)}\n`);
4977
+ }
4978
+ else {
4979
+ stdout(formatAcsConformanceResult(result));
4980
+ }
4981
+ return result.passed ? 0 : 1;
4982
+ }
4714
4983
  if (command === "connect") {
4715
4984
  const parsed = parseConnectArgs(args);
4716
4985
  const result = await runConnect({
@@ -5627,6 +5896,39 @@ function parseTestPolicyArgs(args) {
5627
5896
  }
5628
5897
  return parsed;
5629
5898
  }
5899
+ function parseAcsArgs(args) {
5900
+ const subcommand = args[0];
5901
+ if (subcommand !== "conformance") {
5902
+ throw new Error("acs_requires_conformance");
5903
+ }
5904
+ const parsed = {
5905
+ subcommand,
5906
+ json: false,
5907
+ };
5908
+ for (let index = 1; index < args.length; index += 1) {
5909
+ const arg = args[index];
5910
+ switch (arg) {
5911
+ case "--config":
5912
+ parsed.configPath = requireValue(args, index, "--config");
5913
+ index += 1;
5914
+ break;
5915
+ case "--ledger":
5916
+ parsed.ledgerPath = requireValue(args, index, "--ledger");
5917
+ index += 1;
5918
+ break;
5919
+ case "--cwd":
5920
+ parsed.cwd = requireValue(args, index, "--cwd");
5921
+ index += 1;
5922
+ break;
5923
+ case "--json":
5924
+ parsed.json = true;
5925
+ break;
5926
+ default:
5927
+ throw new Error(`unknown_acs_flag:${arg}`);
5928
+ }
5929
+ }
5930
+ return parsed;
5931
+ }
5630
5932
  function parseSmokeArgs(args) {
5631
5933
  const parsed = {};
5632
5934
  for (let index = 0; index < args.length; index += 1) {
@@ -6000,6 +6302,51 @@ function parseLedgerAttestArgs(args) {
6000
6302
  }
6001
6303
  return parsed;
6002
6304
  }
6305
+ function parseProveEquivalenceArgs(args) {
6306
+ const parsed = {
6307
+ json: false,
6308
+ decisions: [],
6309
+ };
6310
+ for (let index = 0; index < args.length; index += 1) {
6311
+ const arg = args[index];
6312
+ switch (arg) {
6313
+ case "--config":
6314
+ parsed.configPath = requireValue(args, index, "--config");
6315
+ index += 1;
6316
+ break;
6317
+ case "--ledger":
6318
+ parsed.ledgerPath = requireValue(args, index, "--ledger");
6319
+ index += 1;
6320
+ break;
6321
+ case "--cwd":
6322
+ parsed.cwd = requireValue(args, index, "--cwd");
6323
+ index += 1;
6324
+ break;
6325
+ case "--from":
6326
+ parsed.from = requireValue(args, index, "--from");
6327
+ index += 1;
6328
+ break;
6329
+ case "--to":
6330
+ parsed.to = requireValue(args, index, "--to");
6331
+ index += 1;
6332
+ break;
6333
+ case "--decision":
6334
+ parsed.decisions.push(parseDecisionFlag(requireValue(args, index, "--decision")));
6335
+ index += 1;
6336
+ break;
6337
+ case "--out":
6338
+ parsed.outputPath = requireValue(args, index, "--out");
6339
+ index += 1;
6340
+ break;
6341
+ case "--json":
6342
+ parsed.json = true;
6343
+ break;
6344
+ default:
6345
+ throw new Error(`unknown_prove_equivalence_flag:${arg}`);
6346
+ }
6347
+ }
6348
+ return parsed;
6349
+ }
6003
6350
  function parseVerifyExportArgs(args) {
6004
6351
  const parsed = { bundlePath: "", json: false };
6005
6352
  for (let index = 0; index < args.length; index += 1) {
@@ -6328,6 +6675,13 @@ function parseDemoArgs(args) {
6328
6675
  parsed.templateId = requireValue(args, index, "--template");
6329
6676
  index += 1;
6330
6677
  break;
6678
+ case "--approve-step-up":
6679
+ parsed.approveStepUp = true;
6680
+ break;
6681
+ case "--approved-by":
6682
+ parsed.approvedBy = requireValue(args, index, "--approved-by");
6683
+ index += 1;
6684
+ break;
6331
6685
  case "--json":
6332
6686
  parsed.json = true;
6333
6687
  break;
@@ -6536,6 +6890,14 @@ function prepareTamperedSlackStep(actions, approvedBy) {
6536
6890
  approvedPayloadHash: artifact.payloadHash,
6537
6891
  attemptedPayloadHash: hashPayload(tamperedPayload),
6538
6892
  mutation: "Slack message payload mutated after exact approval while reusing the original approval artifact.",
6893
+ scopedAuthority: {
6894
+ tool: target.action.capability.tool,
6895
+ resource: target.action.capability.resource,
6896
+ approvedBy: artifact.approvedBy,
6897
+ artifactId: artifact.id,
6898
+ scopeUnchanged: target.action.capability.tool === approvedAction.capability.tool &&
6899
+ target.action.capability.resource === approvedAction.capability.resource,
6900
+ },
6539
6901
  };
6540
6902
  }
6541
6903
  function assertRealWorkflowModes(loadedConfig) {
@@ -6796,6 +7158,36 @@ function formatPolicyParityResult(result) {
6796
7158
  ["fixtures", result.fixturesPath],
6797
7159
  ["cedar", result.cedarPath],
6798
7160
  ["rego", result.regoWasmPath],
7161
+ [
7162
+ "agentcore",
7163
+ `${result.agentCore.status} · ${result.agentCore.fixtureCount} Cedar-shaped Gateway requests`,
7164
+ ],
7165
+ ],
7166
+ });
7167
+ }
7168
+ function formatAcsConformanceResult(result) {
7169
+ return renderWorkflow({
7170
+ command: "acs conformance",
7171
+ state: result.passed ? "ok" : "failed",
7172
+ verdict: result.passed
7173
+ ? `All ${result.total} ACS intervention vectors passed`
7174
+ : `${result.failed} of ${result.total} ACS intervention vectors failed`,
7175
+ steps: result.vectors.map((entry) => ({
7176
+ state: entry.passed ? "ok" : "failed",
7177
+ name: entry.id,
7178
+ detail: `${entry.interventionPoint} -> ${entry.actualVerdict}` +
7179
+ ` · proxy ${entry.actualProxyStatus ?? "none"}` +
7180
+ ` · ${entry.reasons.join(", ")}`,
7181
+ })),
7182
+ rows: [
7183
+ ["manifest", result.manifestVersion],
7184
+ ["ledger", result.ledgerPath],
7185
+ [
7186
+ "actionpass",
7187
+ result.vectors.some((entry) => entry.actionPassId)
7188
+ ? "issued for allow vector"
7189
+ : "not issued",
7190
+ ],
6799
7191
  ],
6800
7192
  });
6801
7193
  }
@@ -6850,17 +7242,41 @@ function formatLedgerAttestResult(result) {
6850
7242
  ],
6851
7243
  });
6852
7244
  }
7245
+ /**
7246
+ * One line summarizing the approval↔execution equivalence over a set of
7247
+ * execution records. Verification fails closed on any failed pair, so a
7248
+ * summary rendered for a VALID export can only contain proved + unproven.
7249
+ */
7250
+ export function equivalenceSummaryLine(report) {
7251
+ if (report.executions === 0) {
7252
+ return "no execution records";
7253
+ }
7254
+ const parts = [
7255
+ `${report.proved} of ${report.executions} execution${report.executions === 1 ? "" : "s"} proved approved == executed`,
7256
+ ];
7257
+ if (report.unproven > 0) {
7258
+ parts.push(`${report.unproven} without approval evidence (unproven)`);
7259
+ }
7260
+ if (report.failed > 0) {
7261
+ parts.push(`${report.failed} FAILED`);
7262
+ }
7263
+ return parts.join(" · ");
7264
+ }
6853
7265
  function formatVerifyExportResult(result) {
6854
7266
  if (result.result.valid) {
7267
+ const rows = [
7268
+ ["bundle", result.bundlePath],
7269
+ ["issuer", result.result.claims.iss],
7270
+ ["export digest", shortHash(result.result.recomputedDigest)],
7271
+ ];
7272
+ if (result.equivalence) {
7273
+ rows.push(["equivalence", equivalenceSummaryLine(result.equivalence)]);
7274
+ }
6855
7275
  return renderWorkflow({
6856
7276
  command: "verify-export",
6857
7277
  state: "ok",
6858
7278
  verdict: `Export VALID · ${result.result.claims.recordCount} record${result.result.claims.recordCount === 1 ? "" : "s"} independently verified`,
6859
- rows: [
6860
- ["bundle", result.bundlePath],
6861
- ["issuer", result.result.claims.iss],
6862
- ["export digest", shortHash(result.result.recomputedDigest)],
6863
- ],
7279
+ rows,
6864
7280
  });
6865
7281
  }
6866
7282
  return renderWorkflow({
@@ -6873,6 +7289,35 @@ function formatVerifyExportResult(result) {
6873
7289
  ],
6874
7290
  });
6875
7291
  }
7292
+ function formatProveEquivalenceResult(result) {
7293
+ const report = result.report;
7294
+ const failed = report.entries.filter((entry) => entry.status === "failed");
7295
+ const stepRows = failed.map((entry) => ({
7296
+ state: "failed",
7297
+ name: `record ${entry.line}${entry.tool ? ` (${entry.tool})` : ""}`,
7298
+ detail: `${entry.failure} · approved ${entry.approvedPayloadHash ? shortHash(entry.approvedPayloadHash) : "absent"} vs executed ${entry.executedPayloadHash ? shortHash(entry.executedPayloadHash) : "absent"}`,
7299
+ }));
7300
+ return renderWorkflow({
7301
+ command: "prove-equivalence",
7302
+ state: report.failed > 0 ? "failed" : "ok",
7303
+ verdict: report.failed > 0
7304
+ ? `Equivalence FAILED · ${report.failed} execution record${report.failed === 1 ? "" : "s"} broke approved == executed`
7305
+ : report.executions === 0
7306
+ ? "No execution records to prove · the ledger chain is intact"
7307
+ : report.proved === 0
7308
+ ? `No approved executions to prove · ${report.unproven} execution${report.unproven === 1 ? "" : "s"} without approval evidence · chain intact`
7309
+ : `Equivalence holds · ${equivalenceSummaryLine(report)}`,
7310
+ steps: stepRows,
7311
+ rows: [
7312
+ ["ledger", result.ledgerPath],
7313
+ ["executions", report.executions],
7314
+ ["proved", report.proved],
7315
+ ["unproven", report.unproven],
7316
+ ["failed", report.failed],
7317
+ ...(result.outputPath ? [["out", result.outputPath]] : []),
7318
+ ],
7319
+ });
7320
+ }
6876
7321
  /** Map a per-connector readiness status to a render state. */
6877
7322
  function doctorProviderState(status) {
6878
7323
  if (status === "ready")
@@ -6919,6 +7364,20 @@ function formatConnectorDoctorResult(result) {
6919
7364
  * step failed before Slack, so the tamper was never exercised — must never be
6920
7365
  * reported as the tampered payload executing).
6921
7366
  */
7367
+ /**
7368
+ * The scoped-authority contrast line — the differentiation beat spelled out:
7369
+ * the agent still held a valid, exact-payload human approval whose tool +
7370
+ * resource scope matched the attempted call, so an authorizer that checks
7371
+ * scope alone would have let the swapped payload through. Only claim that
7372
+ * when `scopeUnchanged` was actually computed true; otherwise state the
7373
+ * scope changed too, which any authorizer should reject.
7374
+ */
7375
+ export function scopedAuthorityLine(authority) {
7376
+ const held = `valid approval by ${authority.approvedBy} for ${authority.tool} on ${authority.resource}`;
7377
+ return authority.scopeUnchanged
7378
+ ? `${held} · scope unchanged - a scope-only check would allow this call; Axtary binds the approval to the exact payload hash`
7379
+ : `${held} · scope changed by the mutation`;
7380
+ }
6922
7381
  export function tamperOutcomeLine(tamper) {
6923
7382
  if (!tamper.reached) {
6924
7383
  return "tamper step not reached - a prior step failed before Slack, so content binding was not exercised this run (re-run with a fresh --head). The tampered payload did NOT execute.";
@@ -6953,6 +7412,7 @@ function formatRealWorkflowRunResult(result) {
6953
7412
  ? "executed"
6954
7413
  : "failed";
6955
7414
  rows.push(["tamper", `${result.tamper.step} (${result.tamper.tool}) · ${result.tamper.mutation}`]);
7415
+ rows.push(["scoped authority", scopedAuthorityLine(result.tamper.scopedAuthority)]);
6956
7416
  rows.push(["approved hash", shortHash(result.tamper.approvedPayloadHash)]);
6957
7417
  rows.push(["attempted hash", shortHash(result.tamper.attemptedPayloadHash)]);
6958
7418
  rows.push(["tamper outcome", `${stateGlyph(tamperState)} ${tamperOutcomeLine(result.tamper)}`]);
@@ -7349,7 +7809,7 @@ function helpText() {
7349
7809
  return [
7350
7810
  "Usage:",
7351
7811
  " axtary init [--template repo-only-coding|staging-reads|incident-investigation|ticket-updates|doc-search|guarded-prod] [--config axtary.yml] [--force] [--json] (scaffold a starter config and optional scope-template harness)",
7352
- " axtary demo [--config axtary.yml] [--template <name>] [--ledger .axtary/actions.jsonl] [--json]",
7812
+ " axtary demo [--config axtary.yml] [--template <name>] [--ledger .axtary/actions.jsonl] [--approve-step-up [--approved-by user:you@example.com]] [--json] (--approve-step-up attaches a local exact-payload approval so step-up actions execute with the approved/executed hash pair recorded)",
7353
7813
  " axtary revoke <actionpass-jti> [--trust-store .axtary/actionpass-trust-store.json] [--status-store .axtary/actionpass-status.json] [--by user:you@example.com] [--reason 'task cancelled'] [--json]",
7354
7814
  " axtary revocations [--trust-store .axtary/actionpass-trust-store.json] [--json]",
7355
7815
  " axtary issuer-keys [--keyring .axtary/keys/actionpass-issuer-keyring.json] [--rotate] [--retire-after 1200] (metadata + public JWKS only; never prints private keys)",
@@ -7370,7 +7830,8 @@ function helpText() {
7370
7830
  " axtary sync-ledger --endpoint https://app.example/api/ledger/sync [--config axtary.yml] [--ledger .axtary/actions.jsonl] [--from ISO] [--to ISO] [--decision allow|deny|step_up] [--tenant org:company] [--token-env AXTARY_LEDGER_SYNC_TOKEN] [--json]",
7371
7831
  " axtary export-otel --endpoint http://127.0.0.1:4318/v1/traces [--config axtary.yml] [--ledger .axtary/actions.jsonl] [--from ISO] [--to ISO] [--decision allow|deny|step_up] [--service-name axtary-local] [--json] # opt-in OTLP/HTTP GenAI execute_tool spans, payload-free",
7372
7832
  " axtary attest-ledger [--config axtary.yml] [--ledger .axtary/actions.jsonl] [--from ISO] [--to ISO] [--decision allow|deny|step_up] [--out attestation.json] [--key .axtary/keys/attestation-key.json] [--json]",
7373
- " axtary verify-export <attestation.json> [--issuer https://local.axtary.dev] [--json] # standalone: signature + chain + digest",
7833
+ " axtary verify-export <attestation.json> [--issuer https://local.axtary.dev] [--json] # standalone: signature + chain + digest + approval↔execution equivalence",
7834
+ " axtary prove-equivalence [--config axtary.yml] [--ledger .axtary/actions.jsonl] [--from ISO] [--to ISO] [--decision allow|deny|step_up] [--out equivalence.json] [--json] # per-execution approved_payload_hash == executed_payload_hash proof, exportable",
7374
7835
  " axtary prove-inclusion <attestation.json> (--record <jti> | --index <n>) [--out proof.json] [--json] # RFC 6962 inclusion proof",
7375
7836
  " axtary prove-consistency <first.json> <second.json> [--out proof.json] [--json] # RFC 6962 consistency proof across two heads",
7376
7837
  " axtary verify-proof <proof.json> (--bundle <b.json> | --first <a.json> --second <b.json>) [--issuer ...] [--json] # standalone proof verifier",
@@ -7396,6 +7857,7 @@ function helpText() {
7396
7857
  " axtary policy test <policies.test.yml> [--config axtary.yml] [--json] (CI policy harness)",
7397
7858
  " axtary test-policy --fixtures ./fixtures [--config axtary.yml] [--json]",
7398
7859
  " axtary test-policy --parity [--fixtures parity-fixtures.json] [--cedar <file.cedar>] [--rego-wasm <policy.wasm>] [--json]",
7860
+ " axtary acs conformance [--config axtary.yml] [--ledger .axtary/acs-conformance.jsonl] [--json] (credential-free ACS pre_tool_call vectors through Axtary policy, ActionPass, and ledger)",
7399
7861
  "",
7400
7862
  ].join("\n");
7401
7863
  }