@getmarrow/install 0.1.28 → 0.1.30

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.
Files changed (3) hide show
  1. package/README.md +38 -12
  2. package/package.json +15 -1
  3. package/src/installer.js +144 -20
package/README.md CHANGED
@@ -6,6 +6,16 @@ Marrow is the runtime control and proof layer for teams running AI agents. It ap
6
6
 
7
7
  Use `@getmarrow/install` as the default entry point. It detects supported agent and project surfaces, writes the appropriate passive configuration, runs a harmless end-to-end self-test, and reports whether policy, proof, attribution, and outcome capture are active.
8
8
 
9
+ ## Agent Discovery
10
+
11
+ - **Publisher:** Marrow AI. Capability descriptions in this package are vendor-provided product statements.
12
+ - **Use this package for:** automatic harness detection, passive setup, activation proof, the Govern TUI, or a governed command runner.
13
+ - **Product category:** agent governance, runtime control, proof, and fleet intelligence.
14
+ - **Operator boundary:** installing Marrow, creating an account, or submitting a diagnostic requires operator approval.
15
+ - **Public diagnostic privacy:** raw prompts, source code, credentials, customer data, and free-form work descriptions are not accepted.
16
+ - **GitHub retrieval:** https://api.getmarrow.ai/v1/public/discovery/placements/plc_84c601136b8ffe7d26168fd0
17
+ - **npm retrieval:** https://api.getmarrow.ai/v1/public/discovery/placements/plc_9f0bcf8ed316691aff5089c2
18
+
9
19
  ## Install
10
20
 
11
21
  ```bash
@@ -18,16 +28,23 @@ Required secret:
18
28
  export MARROW_API_KEY=mrw_live_...
19
29
  ```
20
30
 
21
- ## What's New in v0.1.28
31
+ ## What's New in v0.1.30
32
+
33
+ v0.1.30 adds a consistent machine-readable distribution contract while preserving the server-verified first-run activation introduced in v0.1.29:
22
34
 
23
- v0.1.28 aligns first-run documentation with Marrow's business product contract:
35
+ - GitHub and npm now advertise separate signed discovery placements;
36
+ - package metadata identifies the installer as agent governance rather than a general memory utility;
37
+ - the README tells agents and operators when to use the installer and what public discovery will not collect;
38
+ - `npx @getmarrow/install activate` detects the current harness, writes supported passive controls, creates and closes a harmless decision, and asks Marrow to verify that exact outcome;
39
+ - activation succeeds only when the API returns a tenant-scoped activation receipt;
40
+ - the receipt reports capture, before-action intervention, outcome closure, and first-value state;
41
+ - existing setup, governed runner, and TUI commands remain compatible.
24
42
 
25
- - runtime control before consequential actions;
26
- - proof and outcome closure afterward;
27
- - tenant-scoped fleet improvement across interchangeable agents and harnesses;
28
- - clear separation between the default installer, advanced SDK integration, and MCP-native integration.
43
+ Use `activate` when you want one command with an explicit success contract. Use `--yes` when an existing automation already handles setup prompts and verification output.
29
44
 
30
- This patch changes package documentation and positioning. Existing installer, governed runner, and TUI behavior remains compatible.
45
+ ```bash
46
+ npx @getmarrow/install activate
47
+ ```
31
48
 
32
49
  ## What It Detects
33
50
 
@@ -41,18 +58,19 @@ The installer detects supported configuration and project signals for:
41
58
 
42
59
  Marrow does not replace these models or harnesses. It adds a common business control, proof, and outcome layer around the actions they perform.
43
60
 
44
- ## First-Run Verification
61
+ ## First-Run Activation
45
62
 
46
- With a valid key, setup:
63
+ With a valid key, `activate`:
47
64
 
48
65
  1. detects the local integration surfaces;
49
66
  2. writes supported config and passive instructions;
50
67
  3. creates a harmless test decision;
51
68
  4. closes its outcome;
52
- 5. reads agent status and the one-call runtime;
53
- 6. reports captured surfaces, attribution quality, proof coverage, token-value capture state, and the exact next action.
69
+ 5. sends the exact self-test decision ID to Marrow for server-side verification;
70
+ 6. reads agent status and the one-call runtime;
71
+ 7. returns an activation receipt with capture, intervention, closure, first-value state, and the exact next action.
54
72
 
55
- Healthy output confirms that Marrow is active instead of only confirming that files were written.
73
+ Healthy output confirms the exact decision outcome exists under the authenticated account and agent. A local file write or client-supplied `verified: true` value cannot produce an active receipt.
56
74
 
57
75
  ## Govern TUI
58
76
 
@@ -120,6 +138,14 @@ The fleet view shows live agents, active workflows, risky actions waiting for pr
120
138
 
121
139
  These are integration surfaces for one Marrow product, not separate products.
122
140
 
141
+ ## Always-On Lifecycle
142
+
143
+ Supported integrations capture a compact lifecycle without storing raw prompts, completions, command output, tool output, or credentials. Marrow recognizes prompt, goal, pre-action, tool/command result, verification evidence, workflow/session, subagent, handoff, proof-pack, and outcome events.
144
+
145
+ Meaningful work opens an outcome-closure item. A tool exit or workflow completion does not silently count as a successful business outcome; an explicit outcome receipt closes it. Transient delivery failures are held in an owner-only local spool and retried with the same event ID so retries do not create duplicate lifecycle records.
146
+
147
+ Owners can inspect pending outcomes in Fleet Operations. Agents can retrieve a tenant-scoped causal trace for a decision to see the prior failure, lesson, gate, proof, workflow, and outcome path that changed the action.
148
+
123
149
  ## Passive Token and Value Proof
124
150
 
125
151
  When the installer writes `.marrow/passive-runtime.mjs` and the harness exposes usage metadata, Marrow can capture compact provider/model, token, latency, and optional cost counts. It does not require raw prompts, completions, command output, tool output, or plaintext secrets.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getmarrow/install",
3
- "version": "0.1.28",
3
+ "version": "0.1.30",
4
4
  "description": "Universal installer and governed runner for Marrow agent fleets.",
5
5
  "bin": {
6
6
  "marrow-install": "bin/marrow-install.js"
@@ -24,6 +24,20 @@
24
24
  "type": "git",
25
25
  "url": "git+https://github.com/getmarrow/marrow-install.git"
26
26
  },
27
+ "homepage": "https://getmarrow.ai/docs/",
28
+ "bugs": {
29
+ "url": "https://github.com/getmarrow/marrow-install/issues"
30
+ },
31
+ "marrow": {
32
+ "category": "agent-governance",
33
+ "role": "universal-installer-and-governed-runner",
34
+ "operatorApprovalRequired": true,
35
+ "privacy": "No raw prompts, source code, credentials, or customer data are accepted by public discovery.",
36
+ "discovery": {
37
+ "github": "https://api.getmarrow.ai/v1/public/discovery/placements/plc_84c601136b8ffe7d26168fd0",
38
+ "npm": "https://api.getmarrow.ai/v1/public/discovery/placements/plc_9f0bcf8ed316691aff5089c2"
39
+ }
40
+ },
27
41
  "license": "MIT",
28
42
  "files": [
29
43
  "bin",
package/src/installer.js CHANGED
@@ -48,12 +48,19 @@ function parseArgs(argv) {
48
48
  baseUrl: process.env.MARROW_BASE_URL || DEFAULT_BASE_URL,
49
49
  agentId: process.env.MARROW_FLEET_AGENT_ID || process.env.MARROW_AGENT_ID || '',
50
50
  selfTest: true,
51
+ selfTestExplicitlyDisabled: false,
51
52
  json: false,
53
+ activate: false,
52
54
  };
53
55
 
54
56
  for (let i = 0; i < argv.length; i += 1) {
55
57
  const arg = argv[i];
56
- if (arg === '--yes' || arg === '-y') options.yes = true;
58
+ if (arg === 'activate' || arg === '--activate') {
59
+ options.activate = true;
60
+ options.yes = true;
61
+ options.selfTest = true;
62
+ }
63
+ else if (arg === '--yes' || arg === '-y') options.yes = true;
57
64
  else if (arg === '--repair' || arg === 'repair') {
58
65
  options.repair = true;
59
66
  options.yes = true;
@@ -61,7 +68,10 @@ function parseArgs(argv) {
61
68
  else if (arg === '--dry-run') options.dryRun = true;
62
69
  else if (arg === '--doctor' || arg === 'doctor' || arg === 'check') options.doctor = true;
63
70
  else if (arg === '--json') options.json = true;
64
- else if (arg === '--no-self-test') options.selfTest = false;
71
+ else if (arg === '--no-self-test') {
72
+ options.selfTest = false;
73
+ options.selfTestExplicitlyDisabled = true;
74
+ }
65
75
  else if (arg === '--self-test') options.selfTest = true;
66
76
  else if (arg === '--cwd') options.cwd = path.resolve(argv[++i] || options.cwd);
67
77
  else if (arg === '--mode') options.mode = argv[++i] || options.mode;
@@ -85,6 +95,12 @@ function parseArgs(argv) {
85
95
  if (!['auto', 'mcp', 'sdk', 'both', 'md'].includes(options.mode)) {
86
96
  throw new Error('--mode must be one of auto, mcp, sdk, both, md');
87
97
  }
98
+ if (options.activate && options.selfTestExplicitlyDisabled) {
99
+ throw new Error('activate cannot be combined with --no-self-test because server verification is required');
100
+ }
101
+ if (options.activate && options.dryRun) {
102
+ throw new Error('activate cannot be combined with --dry-run; use --dry-run without activate to preview changes');
103
+ }
88
104
 
89
105
  return options;
90
106
  }
@@ -92,6 +108,7 @@ function parseArgs(argv) {
92
108
  function usage() {
93
109
  return `Usage:
94
110
  npx @getmarrow/install --dry-run
111
+ npx @getmarrow/install activate
95
112
  npx @getmarrow/install --yes
96
113
  npx @getmarrow/install --repair
97
114
  npx @getmarrow/install doctor
@@ -99,6 +116,7 @@ function usage() {
99
116
  npx @getmarrow/install --sdk --yes
100
117
 
101
118
  Options:
119
+ activate Detect, install, self-test, and return a server-confirmed activation receipt
102
120
  --dry-run Print planned changes without writing
103
121
  --doctor Check install health without writing
104
122
  --repair Write missing hooks/config, then run self-test and status check
@@ -111,6 +129,20 @@ Options:
111
129
  `;
112
130
  }
113
131
 
132
+ function stableAgentId(root, client = sourceClient()) {
133
+ const identity = `${path.resolve(root)}:${os.hostname()}:${client}`;
134
+ return `${client}-${crypto.createHash('sha256').update(identity).digest('hex').slice(0, 12)}`;
135
+ }
136
+
137
+ function detectedClient(detection) {
138
+ if (sourceClient() !== 'custom') return sourceClient();
139
+ if (detection.openclaw) return 'openclaw';
140
+ if (detection.claudeCode) return 'claude-code';
141
+ if (detection.cursor) return 'cursor';
142
+ if (detection.codex) return 'codex';
143
+ return 'custom';
144
+ }
145
+
114
146
  function exists(filePath) {
115
147
  return fs.existsSync(filePath);
116
148
  }
@@ -556,8 +588,15 @@ function applyPlan(plan, options) {
556
588
  }
557
589
 
558
590
  const changed = before !== after;
559
- changes.push({ path: write.path, label: write.label, changed });
560
- if (changed && options.yes && !options.dryRun && !options.doctor) {
591
+ const writeApplied = Boolean(options.yes && !options.dryRun && !options.doctor);
592
+ changes.push({
593
+ path: write.path,
594
+ label: write.label,
595
+ changed,
596
+ applied: changed && writeApplied,
597
+ already_present: !changed,
598
+ });
599
+ if (changed && writeApplied) {
561
600
  fs.mkdirSync(path.dirname(write.path), { recursive: true });
562
601
  fs.writeFileSync(write.path, after);
563
602
  }
@@ -581,6 +620,22 @@ async function requestJson(url, options) {
581
620
  return json.data || json;
582
621
  }
583
622
 
623
+ function isCanonicalTimestamp(value) {
624
+ if (typeof value !== 'string' || value.length === 0) return false;
625
+ const parsed = Date.parse(value);
626
+ return Number.isFinite(parsed) && new Date(parsed).toISOString() === value;
627
+ }
628
+
629
+ function runtimeGateVerified(runtime) {
630
+ if (!runtime || typeof runtime !== 'object') return false;
631
+ if (runtime.ok === true) return true;
632
+ const gate = runtime.risk_gate;
633
+ if (!gate || typeof gate !== 'object') return false;
634
+ if (typeof gate.allow === 'boolean' || typeof gate.allowed === 'boolean') return true;
635
+ const decision = typeof gate.decision === 'string' ? gate.decision.toLowerCase() : '';
636
+ return ['allow', 'warn', 'review_required', 'block'].includes(decision);
637
+ }
638
+
584
639
  async function runSelfTest(options) {
585
640
  if (!options.selfTest) return { skipped: true, reason: 'disabled' };
586
641
  if (!options.apiKey) {
@@ -595,7 +650,7 @@ async function runSelfTest(options) {
595
650
  authorization: `Bearer ${options.apiKey}`,
596
651
  'content-type': 'application/json',
597
652
  'x-marrow-session-id': `install-${Date.now()}`,
598
- 'x-marrow-client': sourceClient(),
653
+ 'x-marrow-client': options.client || sourceClient(),
599
654
  };
600
655
  if (options.agentId) headers['x-marrow-agent-id'] = options.agentId;
601
656
 
@@ -608,7 +663,7 @@ async function runSelfTest(options) {
608
663
  action: 'Marrow passive install self-test: verify SDK/MCP hooks can record a harmless setup event',
609
664
  source_meta: {
610
665
  channel: 'cli',
611
- client: sourceClient(),
666
+ client: options.client || sourceClient(),
612
667
  user_intent: 'operate',
613
668
  },
614
669
  }),
@@ -641,10 +696,7 @@ async function runSelfTest(options) {
641
696
  outcome: 'self-test outcome committed',
642
697
  },
643
698
  }),
644
- }).catch((error) => ({
645
- ok: false,
646
- error: error instanceof Error ? error.message : String(error),
647
- }));
699
+ });
648
700
  const performance = await requestJson(`${baseUrl}/v1/analytics/agent-performance?period=7`, { headers })
649
701
  .catch((error) => ({
650
702
  ok: false,
@@ -662,11 +714,15 @@ async function runSelfTest(options) {
662
714
  checks: ['installer first-value self-test'],
663
715
  outcome: 'first-value endpoint reached',
664
716
  },
717
+ decision_id: decisionId,
718
+ agent_id: options.agentId,
719
+ activation: options.activation ? {
720
+ ...options.activation,
721
+ intervention_verified: runtimeGateVerified(runtime),
722
+ closure_verified: true,
723
+ } : undefined,
665
724
  }),
666
- }).catch((error) => ({
667
- ok: false,
668
- error: error instanceof Error ? error.message : String(error),
669
- }));
725
+ });
670
726
  const valueProof = await requestJson(`${baseUrl}/v1/agent/value/proof?period_days=30`, { headers })
671
727
  .catch((error) => ({
672
728
  ok: false,
@@ -675,6 +731,30 @@ async function runSelfTest(options) {
675
731
  const tokenValueProof = buildTokenValueProof(valueProof);
676
732
  const firstValueSignal = buildFirstValueSignal(status, runtime, performance, firstValue, tokenValueProof);
677
733
  const installValueMoment = buildInstallValueMoment(firstValueSignal, status, runtime, performance, firstValue, tokenValueProof);
734
+ let activationReceipt = null;
735
+ let activationVerified = false;
736
+ if (options.activation) {
737
+ activationReceipt = firstValue && firstValue.activation_receipt;
738
+ const receiptValid = activationReceipt
739
+ && typeof activationReceipt === 'object'
740
+ && typeof activationReceipt.id === 'string'
741
+ && activationReceipt.id.length > 0
742
+ && activationReceipt.decision_id === decisionId
743
+ && activationReceipt.agent_id === options.agentId
744
+ && activationReceipt.outcome_success === true
745
+ && isCanonicalTimestamp(activationReceipt.outcome_recorded_at)
746
+ && activationReceipt.server_confirmed === true
747
+ && activationReceipt.capture_verified === true
748
+ && activationReceipt.intervention_verified === true
749
+ && activationReceipt.closure_verified === true;
750
+ if (!receiptValid) {
751
+ throw new Error('activation receipt did not verify the exact self-test decision, agent, runtime gate, and closed successful outcome');
752
+ }
753
+ activationVerified = Boolean(firstValue.active && runtimeGateVerified(runtime) && (status.enabled ?? status.ok));
754
+ if (!activationVerified) {
755
+ throw new Error('activation prerequisites were not all verified by the server');
756
+ }
757
+ }
678
758
  return {
679
759
  skipped: false,
680
760
  decision_id: decisionId,
@@ -684,9 +764,11 @@ async function runSelfTest(options) {
684
764
  recommended_fix: status.recommended_fix || null,
685
765
  next_action: status.next_action || null,
686
766
  auto_outcome_closure: status.auto_outcome_closure || null,
687
- runtime_active: Boolean(runtime && runtime.ok !== false),
767
+ runtime_active: runtimeGateVerified(runtime),
688
768
  runtime_exact_next_action: runtime.exact_next_action || null,
689
769
  runtime_before_you_act: runtime.before_you_act || null,
770
+ activation_verified: activationVerified,
771
+ activation_receipt: activationReceipt,
690
772
  first_value: firstValue && firstValue.ok !== false ? firstValue : null,
691
773
  first_value_signal: firstValueSignal,
692
774
  install_value_moment: installValueMoment,
@@ -821,6 +903,12 @@ function printReport(report) {
821
903
  process.stdout.write(`Mode: ${report.mode}\n`);
822
904
  process.stdout.write(`Write mode: ${report.writeMode}\n\n`);
823
905
 
906
+ if (report.activation?.requested) {
907
+ process.stdout.write('Activation:\n');
908
+ process.stdout.write(`- agent: ${report.activation.agent_id}\n`);
909
+ process.stdout.write(`- server confirmed: ${report.activation.server_confirmed ? 'yes' : 'no'}\n\n`);
910
+ }
911
+
824
912
  process.stdout.write('Detected:\n');
825
913
  for (const [key, value] of Object.entries(report.detected)) {
826
914
  process.stdout.write(`- ${key}: ${value ? 'yes' : 'no'}\n`);
@@ -841,6 +929,7 @@ function printReport(report) {
841
929
  process.stdout.write(`- decision_id: ${report.selfTest.decision_id}\n`);
842
930
  process.stdout.write(`- health: ${report.selfTest.health || 'unknown'}\n`);
843
931
  process.stdout.write(`- one-call runtime: ${report.selfTest.runtime_active ? 'active' : 'not verified'}\n`);
932
+ if (report.selfTest.error) process.stdout.write(`- error: ${report.selfTest.error}\n`);
844
933
  if (report.selfTest.next_action) process.stdout.write(`- next action: ${report.selfTest.next_action}\n`);
845
934
  if (report.selfTest.first_value_signal) {
846
935
  process.stdout.write('\nFirst value:\n');
@@ -930,10 +1019,32 @@ function printReport(report) {
930
1019
  }
931
1020
 
932
1021
  async function install(options) {
1022
+ if (options.activate && (options.yes !== true || options.dryRun || options.doctor)) {
1023
+ throw new Error('activate requires write mode (--yes) because hooks must be installed during this run');
1024
+ }
1025
+ if (options.activate && options.selfTest === false) {
1026
+ throw new Error('activate requires the server self-test');
1027
+ }
933
1028
  const detection = detectEnvironment(options.cwd);
934
1029
  const plan = buildPlan(detection, options);
935
1030
  const writeMode = options.doctor ? 'doctor' : options.dryRun ? 'dry-run' : options.repair ? 'repair' : options.yes ? 'write' : 'dry-run';
936
1031
  const changes = applyPlan(plan, options);
1032
+ const client = detectedClient(detection);
1033
+ options.client = client;
1034
+ if (!options.agentId) options.agentId = stableAgentId(detection.root, client);
1035
+ options.activation = options.activate ? {
1036
+ harness: client,
1037
+ agent_id: options.agentId,
1038
+ install_surface: plan.mode,
1039
+ mode: options.governanceMode || 'passive',
1040
+ hooks_installed: changes
1041
+ .filter((change) => change.changed && change.applied && /hook|runtime|rule|instruction|config/i.test(change.label))
1042
+ .map((change) => change.label)
1043
+ .slice(0, 20),
1044
+ capture_verified: changes.every((change) => change.applied),
1045
+ intervention_verified: false,
1046
+ closure_verified: false,
1047
+ } : null;
937
1048
  const configInspection = inspectNpmTokenConfig();
938
1049
  const sdkDependency = inspectSdkDependency(detection);
939
1050
  const configDiagnostics = configInspection.safe;
@@ -941,11 +1052,17 @@ async function install(options) {
941
1052
  ? repairConfigDiagnostics(configDiagnostics)
942
1053
  : [];
943
1054
  const envHints = options.apiKey ? [] : findLikelyEnvFiles(detection);
944
- const selfTest = await runSelfTest(options).catch((error) => ({
945
- skipped: false,
946
- active: false,
947
- error: error instanceof Error ? error.message : String(error),
948
- }));
1055
+ let selfTest;
1056
+ try {
1057
+ selfTest = await runSelfTest(options);
1058
+ } catch (error) {
1059
+ const message = error instanceof Error ? error.message : String(error);
1060
+ if (options.activate) throw new Error(`Marrow activation failed: ${message}`);
1061
+ selfTest = { skipped: false, active: false, error: message };
1062
+ }
1063
+ if (options.activate && !selfTest.activation_verified) {
1064
+ throw new Error('Marrow activation failed: server confirmation was not returned');
1065
+ }
949
1066
  const changedConfig = changes.some((change) => change.changed) || configRepairs.some((repair) => repair.changed);
950
1067
  const selfTestPassed = Boolean(!selfTest.skipped && selfTest.active && !selfTest.error);
951
1068
  const remediation = options.repair
@@ -976,6 +1093,12 @@ async function install(options) {
976
1093
  openclaw: detection.openclaw,
977
1094
  mcpConfig: detection.mcpConfig,
978
1095
  },
1096
+ activation: {
1097
+ requested: options.activate,
1098
+ agent_id: options.agentId,
1099
+ server_confirmed: Boolean(selfTest.activation_verified),
1100
+ receipt: selfTest.activation_receipt || null,
1101
+ },
979
1102
  changes,
980
1103
  doctor: {
981
1104
  active: Boolean(!selfTest.skipped && selfTest.active),
@@ -1026,4 +1149,5 @@ module.exports = {
1026
1149
  inspectSdkDependency,
1027
1150
  buildInstallValueMoment,
1028
1151
  buildTokenValueProof,
1152
+ stableAgentId,
1029
1153
  };