@ouro.bot/cli 0.1.0-alpha.741 → 0.1.0-alpha.743
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/changelog.json +42 -0
- package/deploy/unraid/Dockerfile +7 -2
- package/deploy/unraid/README.txt +717 -106
- package/deploy/unraid/migrate-sanctuary-bundle.mjs +21 -0
- package/deploy/unraid/ouro-events/bootstrap-spool.sh +92 -0
- package/deploy/unraid/ouro-events/emit-event.mjs +310 -0
- package/deploy/unraid/ouro-events/emit-usenet-event.sh +54 -0
- package/deploy/unraid/ouro-events/install-usenet-guard.sh +434 -0
- package/deploy/unraid/ouro-events/usenet-health.sh +178 -0
- package/deploy/unraid/sanctuary-acceptance-contract.json +4 -3
- package/deploy/unraid/sanctuary-unit16-host-broker.mjs +5 -1
- package/deploy/unraid/sanctuary.ouro/agent.json +2 -2
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +2 -2
- package/deploy/unraid/sanctuary.ouro/habits/sanctuary-health.md +1 -1
- package/deploy/unraid/sanctuary.ouro/provider-readiness.json +3 -10
- package/deploy/unraid/sanctuary.ouro/psyche/ASPIRATIONS.md +3 -1
- package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +3 -1
- package/deploy/unraid/sanctuary.ouro/psyche/LORE.md +4 -0
- package/deploy/unraid/sanctuary.ouro/psyche/SOUL.md +6 -2
- package/deploy/unraid/sanctuary.ouro/psyche/TACIT.md +4 -2
- package/deploy/unraid/sanctuary.ouro/state/policy/steward.json +7 -0
- package/deploy/unraid/sanctuary.ouro/tool-profiles.json +19 -17
- package/deploy/unraid/sanctuary.xml +12 -9
- package/dist/arc/cares.js +171 -37
- package/dist/arc/obligations.js +38 -0
- package/dist/heart/awaiting/await-alert.js +4 -2
- package/dist/heart/awaiting/await-expiry.js +39 -4
- package/dist/heart/awaiting/await-parser.js +8 -0
- package/dist/heart/context-loss-gauntlet.js +31 -2
- package/dist/heart/context-loss-sentinel.js +2 -0
- package/dist/heart/core.js +37 -7
- package/dist/heart/cross-chat-delivery.js +1 -0
- package/dist/heart/daemon/container-healthcheck.js +3 -1
- package/dist/heart/daemon/container-runtime.js +13 -0
- package/dist/heart/daemon/container-spec-auditor-main.js +11 -7
- package/dist/heart/daemon/container-spec-auditor.js +38 -11
- package/dist/heart/daemon/daemon-entry.js +19 -5
- package/dist/heart/daemon/daemon.js +154 -22
- package/dist/heart/daemon/sanctuary-acceptance-adapter.js +151 -87
- package/dist/heart/daemon/sanctuary-acceptance-harness.js +26 -29
- package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +42 -51
- package/dist/heart/daemon/sanctuary-bundle-migration.js +398 -0
- package/dist/heart/daemon/sanctuary-scheduler-origin.js +11 -5
- package/dist/heart/daemon/supercronic-supervisor.js +16 -1
- package/dist/heart/external-events/router.js +1025 -43
- package/dist/heart/provider-models.js +10 -3
- package/dist/heart/session-events.js +23 -5
- package/dist/heart/steward-policy.js +302 -0
- package/dist/heart/tool-approval.js +3 -1
- package/dist/heart/turn-context.js +1 -0
- package/dist/mind/context.js +8 -3
- package/dist/mind/pending.js +25 -0
- package/dist/mind/prompt.js +49 -1
- package/dist/repertoire/guardrails.js +2 -1
- package/dist/repertoire/relationship-authorization.js +194 -0
- package/dist/repertoire/tools-awaiting.js +229 -26
- package/dist/repertoire/tools-base.js +15 -0
- package/dist/repertoire/tools-continuity.js +259 -4
- package/dist/repertoire/tools-notes.js +29 -3
- package/dist/repertoire/tools-session.js +219 -4
- package/dist/repertoire/tools-steward-policy.js +88 -0
- package/dist/repertoire/tools-telegram-contacts.js +37 -0
- package/dist/repertoire/tools-unraid.js +113 -6
- package/dist/repertoire/tools.js +104 -9
- package/dist/repertoire/unraid-restart.js +86 -4
- package/dist/senses/private-runtime-worker.js +36 -3
- package/dist/senses/private-runtime.js +310 -63
- package/dist/senses/sanctuary-health-acceptance-probe.js +8 -6
- package/dist/senses/sanctuary-health-runner.js +51 -96
- package/dist/senses/sanctuary-health.js +54 -93
- package/dist/senses/sanctuary-runtime.js +36 -2
- package/dist/senses/sanctuary-sab.js +80 -0
- package/dist/senses/shared-turn.js +64 -9
- package/dist/senses/telegram-admission.js +684 -0
- package/dist/senses/telegram-approval-runtime.js +46 -6
- package/dist/senses/telegram-client.js +236 -72
- package/dist/senses/telegram-effect-adapter.js +787 -0
- package/dist/senses/telegram-entry.js +4 -1
- package/dist/senses/telegram.js +728 -48
- package/npm-shrinkwrap.json +6 -6
- package/package.json +2 -2
|
@@ -106,7 +106,7 @@ function verifySanctuaryPostbootIntegrity(before, after, scenarioHandleDigest, o
|
|
|
106
106
|
const HEALTH_SCENARIO_LABELS = new Set([
|
|
107
107
|
"unit-16f-cron-fingerprint",
|
|
108
108
|
"unit-16g-health-transition",
|
|
109
|
-
"unit-16h-
|
|
109
|
+
"unit-16h-acceptance-delivery-probe",
|
|
110
110
|
]);
|
|
111
111
|
const INTERACTIVE_DRIVER_LABELS = new Set([
|
|
112
112
|
"unit-16l-duplicate-callback",
|
|
@@ -121,27 +121,19 @@ function hash(value) {
|
|
|
121
121
|
}
|
|
122
122
|
const CONTAINMENT_READ_SCOPE = ["ARRAY", "DASHBOARD", "DISK", "DOCKER", "INFO", "LOGS", "NOTIFICATIONS", "SHARE", "VARS"]
|
|
123
123
|
.map((resource) => `${resource}:READ_ANY`).sort();
|
|
124
|
-
const CONTAINMENT_TELEGRAM_PROFILE = ["unraid_list_containers", "unraid_get_container_logs", "unraid_get_storage", "unraid_get_disks", "unraid_get_notifications", "unraid_get_system", "unraid_restart_container", "ponder", "settle", "speak"];
|
|
125
|
-
const CONTAINMENT_PRIVATE_PROFILE = ["send_message", "rest"];
|
|
126
124
|
const CONTAINMENT_EXCLUDED_TOOLS = ["shell", "read_file", "edit_file", "vault_get", "mcp_call", "exec", "credential_get"];
|
|
127
125
|
const CONTAINMENT_AUDIT_PATH = "/home/ouro/AgentBundles/sanctuary.ouro/state/acceptance/telegram-audit-chain.ndjson";
|
|
128
|
-
const CONTAINMENT_WRITE_POLICY = { kind: "required", policyId: "sanctuary.unraid.restart.v1", actionClass: "unraid.container.restart", requiresSoleCall: true };
|
|
129
|
-
const CONTAINMENT_TELEGRAM_SCHEMA_DIGEST = "3c66299a5f70ec82f8795cae47659284e6dbc691ef49002c2fb22edba76c59b6";
|
|
130
|
-
const CONTAINMENT_PRIVATE_SCHEMA_DIGEST = "61b137b2467acbcf22ca7443ee01e71ed970a62728c42aabffbdcb562f4a6a70";
|
|
131
126
|
function exactContainmentAudit(evidence) {
|
|
132
|
-
const digestFields = [evidence.keyInventoryDigest, evidence.telegramSchemaDigest, evidence.privateSchemaDigest, evidence.auditLedgerDigest];
|
|
127
|
+
const digestFields = [evidence.keyInventoryDigest, evidence.telegramProfileDigest, evidence.telegramSchemaDigest, evidence.privateProfileDigest, evidence.privateSchemaDigest, evidence.auditLedgerDigest, evidence.writeApprovalPolicyDigest];
|
|
133
128
|
return evidence.schemaVersion === "sanctuary-containment-audit-v1"
|
|
134
129
|
&& evidence.keyCount === 2
|
|
135
130
|
&& evidence.readScopeDigest === hash(CONTAINMENT_READ_SCOPE)
|
|
136
131
|
&& evidence.writeScopeDigest === hash([...CONTAINMENT_READ_SCOPE, "DOCKER:UPDATE_ANY"].sort())
|
|
137
132
|
&& evidence.keyRoleAssignmentCount === 0
|
|
138
|
-
&& evidence.telegramToolCount
|
|
139
|
-
&& evidence.
|
|
140
|
-
&& evidence.
|
|
141
|
-
&& evidence.
|
|
142
|
-
&& evidence.privateProfileDigest === hash(CONTAINMENT_PRIVATE_PROFILE)
|
|
143
|
-
&& evidence.privateSchemaDigest === CONTAINMENT_PRIVATE_SCHEMA_DIGEST
|
|
144
|
-
&& evidence.resolvedHandlerCount === CONTAINMENT_TELEGRAM_PROFILE.length + CONTAINMENT_PRIVATE_PROFILE.length
|
|
133
|
+
&& Number.isSafeInteger(evidence.telegramToolCount) && evidence.telegramToolCount > 0
|
|
134
|
+
&& Number.isSafeInteger(evidence.privateToolCount) && evidence.privateToolCount > 0
|
|
135
|
+
&& evidence.relationshipProfilesExact && evidence.handlersExact
|
|
136
|
+
&& evidence.resolvedHandlerCount === evidence.telegramToolCount + evidence.privateToolCount
|
|
145
137
|
&& evidence.excludedToolCount === CONTAINMENT_EXCLUDED_TOOLS.length
|
|
146
138
|
&& evidence.excludedSchemaIntersectionCount === 0
|
|
147
139
|
&& evidence.fabricatedHandlerInvocationCount === 0
|
|
@@ -151,10 +143,10 @@ function exactContainmentAudit(evidence) {
|
|
|
151
143
|
&& evidence.globallyResolvableExcludedToolCount >= 1
|
|
152
144
|
&& evidence.auditPathDigest === (0, node_crypto_1.createHash)("sha256").update(CONTAINMENT_AUDIT_PATH).digest("hex")
|
|
153
145
|
&& evidence.auditRecordCount >= 2 && evidence.auditLifecyclePairCount >= 1
|
|
154
|
-
&& evidence.containerUser === "10001:10001" && evidence.liveProcessUser === "10001:10001" && evidence.mountCount ===
|
|
146
|
+
&& evidence.containerUser === "10001:10001" && evidence.liveProcessUser === "10001:10001" && evidence.mountCount === 4 && evidence.publishedPortCount === 0
|
|
155
147
|
&& evidence.networkMode === "host" && evidence.readOnlyRoot && evidence.mountsExact && evidence.securityExact && evidence.updaterDisabled
|
|
156
148
|
&& !evidence.writableKeyExposure && evidence.rawWriteMaterialFieldCount === 0 && evidence.typedWriteExecutorCount === 1
|
|
157
|
-
&& evidence.
|
|
149
|
+
&& evidence.writeApprovalPolicyExact && !evidence.sensitiveMaterialObserved
|
|
158
150
|
&& digestFields.every((value) => SHA256.test(value));
|
|
159
151
|
}
|
|
160
152
|
function atomicPrivateJson(filePath, value) {
|
|
@@ -444,12 +436,12 @@ function deriveSanctuaryScenarioAssertions(label, before, after, _now, scenarioH
|
|
|
444
436
|
case "unit-16b-runtime-vault-containment":
|
|
445
437
|
if (!after.container)
|
|
446
438
|
return null;
|
|
447
|
-
if (!after.container.autostartExact || !after.container.exactImage || after.container.manualAuthRequired
|
|
448
|
-
|| after.container.mountCount !==
|
|
439
|
+
if (!after.container.autostartExact || !after.container.exactImage || !after.container.mountsExact || after.container.manualAuthRequired
|
|
440
|
+
|| after.container.mountCount !== 4 || Number(after.container.user.split(":")[0]) !== 10001
|
|
449
441
|
|| after.container.publishedPortCount !== 0 || !after.container.readOnlyRoot
|
|
450
442
|
|| !after.container.updaterDisabled || !after.container.vaultUnlocked)
|
|
451
443
|
return null;
|
|
452
|
-
return { autostartExact: after.container.autostartExact, exactImage: after.container.exactImage, manualAuthRequired: after.container.manualAuthRequired, mountCount: after.container.mountCount, nonRootUid: Number(after.container.user.split(":")[0]), publishedPortCount: after.container.publishedPortCount, readOnlyRoot: after.container.readOnlyRoot, updaterDisabled: after.container.updaterDisabled, vaultUnlocked: after.container.vaultUnlocked };
|
|
444
|
+
return { autostartExact: after.container.autostartExact, exactImage: after.container.exactImage, manualAuthRequired: after.container.manualAuthRequired, mountCount: after.container.mountCount, mountsExact: after.container.mountsExact, nonRootUid: Number(after.container.user.split(":")[0]), publishedPortCount: after.container.publishedPortCount, readOnlyRoot: after.container.readOnlyRoot, updaterDisabled: after.container.updaterDisabled, vaultUnlocked: after.container.vaultUnlocked };
|
|
453
445
|
case "unit-16c-provider-readiness":
|
|
454
446
|
return after.provider && after.provider.outwardReady && after.provider.innerReady && after.provider.geminiCandidateReady && after.provider.providersDistinct && !after.provider.silentFallback
|
|
455
447
|
&& after.provider.credentialRevisionsPresent === true && after.provider.requestSemanticsExact === true && after.provider.fallbackAttemptCount === 0
|
|
@@ -463,45 +455,41 @@ function deriveSanctuaryScenarioAssertions(label, before, after, _now, scenarioH
|
|
|
463
455
|
if (telegramResponses !== 1 || newTurns.length !== 1 || !exactFreshTelegramLifecycle(before, after, newTurns[0].updateDigest) || deliveredTurns[0].toolInvocationCount !== 1 || deliveredTurns[0].toolResultDigests.length !== 1 || !turnHasGroundedRead(after, deliveredTurns[0], "unraid_get_storage") || !exactGroundedResponse(after, deliveredTurns[0], "unraid_get_storage"))
|
|
464
456
|
return null;
|
|
465
457
|
return { accurate: true, authorized: true, grounded: true, liveFactsMatched: true, mutationCount: scenarioMutationCount, responseCount: telegramResponses, responseWithinLimit: true, telegramDelivered: true };
|
|
466
|
-
case "unit-16d-2-
|
|
467
|
-
if (before.events.length > after.events.length || before.events.some((entry, index) => hash(entry) !== hash(after.events[index])))
|
|
468
|
-
return null;
|
|
458
|
+
case "unit-16d-2-unknown-admission": {
|
|
469
459
|
const freshEvents = recordsAdded(before.events, after.events, (entry) => hash(entry));
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
const
|
|
474
|
-
const distinctAccount = drop.meta.distinctAccount === true
|
|
475
|
-
&& typeof drop.meta.scenarioHandleDigest === "string" && SHA256.test(drop.meta.scenarioHandleDigest)
|
|
476
|
-
&& typeof drop.meta.updateDigest === "string" && SHA256.test(drop.meta.updateDigest)
|
|
477
|
-
&& typeof drop.meta.senderIdentityDigest === "string" && SHA256.test(drop.meta.senderIdentityDigest)
|
|
478
|
-
&& typeof drop.meta.authorizedIdentityDigest === "string" && SHA256.test(drop.meta.authorizedIdentityDigest)
|
|
479
|
-
&& drop.meta.senderIdentityDigest !== drop.meta.authorizedIdentityDigest
|
|
480
|
-
&& drop.meta.senderDistinct === true
|
|
481
|
-
&& typeof drop.meta.nextOffsetDigest === "string" && SHA256.test(drop.meta.nextOffsetDigest)
|
|
482
|
-
&& typeof drop.meta.dropMac === "string" && SHA256.test(drop.meta.dropMac);
|
|
460
|
+
const beforeAdmissions = before.telegramAdmissions ?? [];
|
|
461
|
+
const afterAdmissions = after.telegramAdmissions ?? [];
|
|
462
|
+
const newAdmissions = recordsAdded(beforeAdmissions, afterAdmissions, (entry) => entry.admissionDigest);
|
|
463
|
+
const admission = newAdmissions.length === 1 ? newAdmissions[0] : null;
|
|
483
464
|
const providerInvocationCount = newTurns.reduce((sum, turn) => sum + turn.providerTurnCount, 0);
|
|
484
465
|
const toolInvocationCount = newTurns.reduce((sum, turn) => sum + turn.toolInvocationCount, 0);
|
|
485
466
|
const durableToolRecordCount = delta(after, before, "senses.sanctuary_read_receipt");
|
|
486
467
|
const workItemCount = newApprovals.length;
|
|
487
|
-
const
|
|
488
|
-
&& typeof before.identity.sessionSurfaceDigest === "string" && SHA256.test(before.identity.sessionSurfaceDigest)
|
|
489
|
-
&& before.identity.sessionSurfaceDigest === after.identity.sessionSurfaceDigest
|
|
490
|
-
&& typeof before.identity.friendSurfaceDigest === "string" && SHA256.test(before.identity.friendSurfaceDigest)
|
|
491
|
-
&& before.identity.friendSurfaceDigest === after.identity.friendSurfaceDigest);
|
|
492
|
-
const forbiddenWorkEvent = freshEvents.some((entry) => entry.event === "senses.telegram_turn_start" || entry.event === "senses.telegram_turn_end"
|
|
493
|
-
|| entry.event === "senses.telegram_turn_error" || entry.event === "senses.sanctuary_read_receipt"
|
|
468
|
+
const forbiddenWorkEvent = freshEvents.some((entry) => entry.event.startsWith("senses.telegram_turn_") || entry.event === "senses.sanctuary_read_receipt"
|
|
494
469
|
|| entry.event.startsWith("senses.telegram_approved_restart_") || entry.event === "senses.sanctuary_health_delivered");
|
|
495
470
|
const offsetAdvanced = Number.isSafeInteger(before.telegramNextUpdateId) && Number.isSafeInteger(after.telegramNextUpdateId)
|
|
496
471
|
&& Number(after.telegramNextUpdateId) === Number(before.telegramNextUpdateId) + 1;
|
|
497
|
-
const
|
|
472
|
+
const exactQuarantine = admission?.status === "pending" && admission.identityExact && admission.acknowledgementExact && admission.ownerCardExact
|
|
473
|
+
&& admission.contentQuarantined && admission.relationshipAbsent && admission.capturedAt <= admission.updatedAt;
|
|
474
|
+
const zeroAgentWork = Boolean(before.zeroWork && after.zeroWork
|
|
498
475
|
&& before.zeroWork.providerToolDigest === after.zeroWork.providerToolDigest
|
|
499
|
-
&& before.zeroWork.outwardDigest === after.zeroWork.outwardDigest
|
|
500
476
|
&& before.zeroWork.approvalMutationDigest === after.zeroWork.approvalMutationDigest
|
|
501
477
|
&& before.zeroWork.sessionFriendDigest === after.zeroWork.sessionFriendDigest);
|
|
502
|
-
if (!
|
|
478
|
+
if (!exactQuarantine || !offsetAdvanced || !zeroAgentWork || newTurns.length !== 0 || telegramResponses !== 0
|
|
479
|
+
|| providerInvocationCount !== 0 || toolInvocationCount !== 0 || workItemCount !== 0 || approvalTransitions !== 0 || newAttempts.length !== 0
|
|
480
|
+
|| scenarioMutationCount !== 0 || durableToolRecordCount !== 0 || forbiddenWorkEvent)
|
|
503
481
|
return null;
|
|
504
|
-
return {
|
|
482
|
+
return {
|
|
483
|
+
acknowledgementSent: true,
|
|
484
|
+
agentTurnCount: 0,
|
|
485
|
+
distinctAccount: true,
|
|
486
|
+
mutationCount: 0,
|
|
487
|
+
ownerCardSent: true,
|
|
488
|
+
providerInvocationCount: 0,
|
|
489
|
+
quarantined: true,
|
|
490
|
+
responseCount: 0,
|
|
491
|
+
workItemCount: 0,
|
|
492
|
+
};
|
|
505
493
|
}
|
|
506
494
|
case "unit-16e-containment-audit":
|
|
507
495
|
if (!after.containment || !exactContainmentAudit(after.containment) || scenarioMutationCount !== 0)
|
|
@@ -515,6 +503,7 @@ function deriveSanctuaryScenarioAssertions(label, before, after, _now, scenarioH
|
|
|
515
503
|
telegramSchemaDigest: after.containment.telegramSchemaDigest,
|
|
516
504
|
privateToolCount: after.containment.privateToolCount, privateProfileDigest: after.containment.privateProfileDigest,
|
|
517
505
|
privateSchemaDigest: after.containment.privateSchemaDigest, resolvedHandlerCount: after.containment.resolvedHandlerCount,
|
|
506
|
+
relationshipProfilesExact: after.containment.relationshipProfilesExact, handlersExact: after.containment.handlersExact,
|
|
518
507
|
excludedToolCount: after.containment.excludedToolCount, excludedSchemaIntersectionCount: after.containment.excludedSchemaIntersectionCount,
|
|
519
508
|
fabricatedHandlerInvocationCount: after.containment.fabricatedHandlerInvocationCount,
|
|
520
509
|
excludedToolAttemptCount: after.containment.excludedToolAttemptCount, excludedToolRejectedCount: after.containment.excludedToolRejectedCount,
|
|
@@ -530,6 +519,7 @@ function deriveSanctuaryScenarioAssertions(label, before, after, _now, scenarioH
|
|
|
530
519
|
rawWriteMaterialFieldCount: after.containment.rawWriteMaterialFieldCount,
|
|
531
520
|
typedWriteExecutorCount: after.containment.typedWriteExecutorCount,
|
|
532
521
|
writeApprovalPolicyDigest: after.containment.writeApprovalPolicyDigest,
|
|
522
|
+
writeApprovalPolicyExact: after.containment.writeApprovalPolicyExact,
|
|
533
523
|
sensitiveMaterialObserved: after.containment.sensitiveMaterialObserved,
|
|
534
524
|
mutationCount: scenarioMutationCount,
|
|
535
525
|
};
|
|
@@ -570,16 +560,17 @@ function deriveSanctuaryScenarioAssertions(label, before, after, _now, scenarioH
|
|
|
570
560
|
return null;
|
|
571
561
|
return { alertCount: 3, productionRestored: true, transitionObserved: true };
|
|
572
562
|
}
|
|
573
|
-
case "unit-16h-
|
|
563
|
+
case "unit-16h-acceptance-delivery-probe": {
|
|
574
564
|
const probe = after.healthProbe;
|
|
575
|
-
if (!after.container?.running || !after.container.healthy || !probe || !healthProbeRestored(probe, after.cron) || probe.
|
|
565
|
+
if (!after.container?.running || !after.container.healthy || !probe || !healthProbeRestored(probe, after.cron) || probe.acceptanceOnly !== true
|
|
566
|
+
|| probe.productionScheduleChanged !== false || probe.clockMode !== "local-daily-boundary" || probe.privateTurnCount !== 1
|
|
576
567
|
|| probe.providerInvocationCount < probe.privateTurnCount || probe.providerInvocationCount > 1_000 || probe.deliveryCount !== 1 || probe.phases.length !== 2
|
|
577
|
-
|| probe.phases[0]?.ordinal !== 1 || probe.phases[0].name !== "
|
|
568
|
+
|| probe.phases[0]?.ordinal !== 1 || probe.phases[0].name !== "delivery-probe-first" || probe.phases[0].trigger !== "acceptance" || probe.phases[0].fixtureStatus !== 503
|
|
578
569
|
|| !probe.phases[0].digestDue || probe.phases[0].deliveryKind !== "digest" || probe.phases[0].deliveryReceiptDigest === null
|
|
579
|
-
|| probe.phases[1]?.ordinal !== 2 || probe.phases[1].name !== "
|
|
570
|
+
|| probe.phases[1]?.ordinal !== 2 || probe.phases[1].name !== "delivery-probe-repeat" || probe.phases[1].trigger !== "acceptance" || probe.phases[1].fixtureStatus !== 503
|
|
580
571
|
|| probe.phases[1].digestDue || probe.phases[1].deliveryKind !== null || probe.phases[1].deliveryReceiptDigest !== null)
|
|
581
572
|
return null;
|
|
582
|
-
return { firedWithinMs: 0, messageCount: 1, productionRestored: true,
|
|
573
|
+
return { acceptanceOnly: true, deliveryPathObserved: true, firedWithinMs: 0, messageCount: 1, productionRestored: true, productionScheduleChanged: false };
|
|
583
574
|
}
|
|
584
575
|
case "unit-16i-delayed-approval":
|
|
585
576
|
if (!exactFreshTelegramLifecycle(before, after) || !intendedRestartApproval(approval) || !completeAttemptLedgerLinked || approval.state !== "succeeded" || mutationCount !== 1 || scenarioMutationCount !== 1 || !restartSucceeded || approval.replayMutationCount !== 0 || !approval.continuationCompleted)
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.SANCTUARY_BUNDLE_ROLLBACK_FILE = exports.SANCTUARY_PACKAGE_MANAGED_FILES = void 0;
|
|
37
|
+
exports.inspectSanctuaryPackageManagedBundleRollback = inspectSanctuaryPackageManagedBundleRollback;
|
|
38
|
+
exports.rollbackSanctuaryPackageManagedBundle = rollbackSanctuaryPackageManagedBundle;
|
|
39
|
+
exports.commitSanctuaryPackageManagedBundle = commitSanctuaryPackageManagedBundle;
|
|
40
|
+
exports.migrateSanctuaryPackageManagedBundle = migrateSanctuaryPackageManagedBundle;
|
|
41
|
+
const fs = __importStar(require("node:fs"));
|
|
42
|
+
const path = __importStar(require("node:path"));
|
|
43
|
+
const node_crypto_1 = require("node:crypto");
|
|
44
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
45
|
+
const steward_policy_1 = require("../steward-policy");
|
|
46
|
+
exports.SANCTUARY_PACKAGE_MANAGED_FILES = [
|
|
47
|
+
"provider-readiness.json",
|
|
48
|
+
"tool-profiles.json",
|
|
49
|
+
"habits/sanctuary-health.md",
|
|
50
|
+
"psyche/ASPIRATIONS.md",
|
|
51
|
+
"psyche/IDENTITY.md",
|
|
52
|
+
"psyche/LORE.md",
|
|
53
|
+
"psyche/SOUL.md",
|
|
54
|
+
"psyche/TACIT.md",
|
|
55
|
+
];
|
|
56
|
+
exports.SANCTUARY_BUNDLE_ROLLBACK_FILE = ".sanctuary-package-managed-rollback.json";
|
|
57
|
+
const SANCTUARY_BUNDLE_COMMITTING_FILE = `${exports.SANCTUARY_BUNDLE_ROLLBACK_FILE}.committing`;
|
|
58
|
+
const BUNDLE_META_VERSION_FIELDS = ["runtimeVersion", "bundleSchemaVersion", "lastUpdated"];
|
|
59
|
+
function readObject(filePath) {
|
|
60
|
+
const value = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
61
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
62
|
+
throw new Error(`${path.basename(filePath)} must contain an object`);
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
function requirePlainFile(filePath, label) {
|
|
66
|
+
let stat;
|
|
67
|
+
try {
|
|
68
|
+
stat = fs.lstatSync(filePath);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
throw new Error(`${label} is missing`);
|
|
72
|
+
}
|
|
73
|
+
if (stat.isSymbolicLink())
|
|
74
|
+
throw new Error(`${label} must not be a symlink`);
|
|
75
|
+
if (!stat.isFile())
|
|
76
|
+
throw new Error(`${label} must be a regular file`);
|
|
77
|
+
}
|
|
78
|
+
function ensureSafeParent(agentRoot, relative) {
|
|
79
|
+
let current = agentRoot;
|
|
80
|
+
for (const segment of path.dirname(relative).split(path.sep).filter((value) => value !== ".")) {
|
|
81
|
+
current = path.join(current, segment);
|
|
82
|
+
if (!fs.existsSync(current))
|
|
83
|
+
fs.mkdirSync(current, { mode: 0o700 });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function validateDestination(agentRoot, relative) {
|
|
87
|
+
let current = agentRoot;
|
|
88
|
+
for (const segment of relative.split(path.sep)) {
|
|
89
|
+
current = path.join(current, segment);
|
|
90
|
+
let stat;
|
|
91
|
+
try {
|
|
92
|
+
stat = fs.lstatSync(current);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (stat.isSymbolicLink())
|
|
98
|
+
throw new Error(`package-managed destination must not be a symlink: ${relative}`);
|
|
99
|
+
if (current !== path.join(agentRoot, relative) && !stat.isDirectory())
|
|
100
|
+
throw new Error(`package-managed destination parent must be a directory: ${relative}`);
|
|
101
|
+
if (current === path.join(agentRoot, relative) && !stat.isFile())
|
|
102
|
+
throw new Error(`package-managed destination must be a regular file: ${relative}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function writeAtomic(filePath, content) {
|
|
106
|
+
const stagingDirectory = fs.mkdtempSync(`${filePath}.package-migration.`);
|
|
107
|
+
const temporary = path.join(stagingDirectory, "value");
|
|
108
|
+
try {
|
|
109
|
+
const fd = fs.openSync(temporary, "wx", 0o600);
|
|
110
|
+
try {
|
|
111
|
+
fs.writeFileSync(fd, content);
|
|
112
|
+
fs.fsyncSync(fd);
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
fs.closeSync(fd);
|
|
116
|
+
}
|
|
117
|
+
fs.renameSync(temporary, filePath);
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
fs.rmSync(temporary, { force: true });
|
|
121
|
+
fs.rmdirSync(stagingDirectory);
|
|
122
|
+
}
|
|
123
|
+
fs.chmodSync(filePath, 0o600);
|
|
124
|
+
const directory = fs.openSync(path.dirname(filePath), fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW);
|
|
125
|
+
try {
|
|
126
|
+
fs.fsyncSync(directory);
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
fs.closeSync(directory);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function cleanupInterruptedWrites(filePath) {
|
|
133
|
+
const parent = path.dirname(filePath);
|
|
134
|
+
const prefix = `${path.basename(filePath)}.package-migration.`;
|
|
135
|
+
if (!fs.existsSync(parent))
|
|
136
|
+
return;
|
|
137
|
+
for (const name of fs.readdirSync(parent)) {
|
|
138
|
+
if (!name.startsWith(prefix))
|
|
139
|
+
continue;
|
|
140
|
+
const stagingDirectory = path.join(parent, name);
|
|
141
|
+
const stagingStat = fs.lstatSync(stagingDirectory);
|
|
142
|
+
if (stagingStat.isSymbolicLink() || !stagingStat.isDirectory())
|
|
143
|
+
throw new Error(`interrupted package migration stage is invalid: ${name}`);
|
|
144
|
+
const entries = fs.readdirSync(stagingDirectory);
|
|
145
|
+
if (entries.some((entry) => entry !== "value"))
|
|
146
|
+
throw new Error(`interrupted package migration stage contains unexpected entries: ${name}`);
|
|
147
|
+
const temporary = path.join(stagingDirectory, "value");
|
|
148
|
+
if (fs.existsSync(temporary)) {
|
|
149
|
+
const temporaryStat = fs.lstatSync(temporary);
|
|
150
|
+
if (temporaryStat.isSymbolicLink() || !temporaryStat.isFile())
|
|
151
|
+
throw new Error(`interrupted package migration value is invalid: ${name}`);
|
|
152
|
+
fs.unlinkSync(temporary);
|
|
153
|
+
}
|
|
154
|
+
fs.rmdirSync(stagingDirectory);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const SNAPSHOT_RELATIVES = [...exports.SANCTUARY_PACKAGE_MANAGED_FILES, "bundle-meta.json"];
|
|
158
|
+
function snapshotDirectoryRelatives() {
|
|
159
|
+
const directories = new Set();
|
|
160
|
+
for (const relative of SNAPSHOT_RELATIVES) {
|
|
161
|
+
let directory = path.dirname(relative);
|
|
162
|
+
while (directory !== ".") {
|
|
163
|
+
directories.add(directory);
|
|
164
|
+
directory = path.dirname(directory);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return [...directories].sort((left, right) => left.length - right.length);
|
|
168
|
+
}
|
|
169
|
+
function captureMigrationSnapshot(agentRoot) {
|
|
170
|
+
const files = SNAPSHOT_RELATIVES.map((relative) => {
|
|
171
|
+
const filePath = path.join(agentRoot, relative);
|
|
172
|
+
if (!fs.existsSync(filePath))
|
|
173
|
+
return { path: filePath, content: null, mode: null };
|
|
174
|
+
const stat = fs.lstatSync(filePath);
|
|
175
|
+
return { path: filePath, content: fs.readFileSync(filePath), mode: stat.mode & 0o777 };
|
|
176
|
+
});
|
|
177
|
+
const directories = snapshotDirectoryRelatives().map((relative) => {
|
|
178
|
+
const directory = path.join(agentRoot, relative);
|
|
179
|
+
if (!fs.existsSync(directory))
|
|
180
|
+
return { path: directory, mode: null };
|
|
181
|
+
const stat = fs.lstatSync(directory);
|
|
182
|
+
return { path: directory, mode: stat.mode & 0o777 };
|
|
183
|
+
});
|
|
184
|
+
return { files, directories };
|
|
185
|
+
}
|
|
186
|
+
function rollbackPath(agentRoot) {
|
|
187
|
+
return path.join(agentRoot, exports.SANCTUARY_BUNDLE_ROLLBACK_FILE);
|
|
188
|
+
}
|
|
189
|
+
function durableSnapshot(agentRoot, snapshot, rollbackImageId, targetImageId) {
|
|
190
|
+
const payload = {
|
|
191
|
+
schemaVersion: 1,
|
|
192
|
+
rollbackImageId,
|
|
193
|
+
targetImageId,
|
|
194
|
+
files: snapshot.files.map((file) => ({ relative: path.relative(agentRoot, file.path), contentBase64: file.content?.toString("base64") ?? null, mode: file.mode })),
|
|
195
|
+
directories: snapshot.directories.map((directory) => ({ relative: path.relative(agentRoot, directory.path), mode: directory.mode })),
|
|
196
|
+
};
|
|
197
|
+
return { ...payload, snapshotDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(payload)).digest("hex") };
|
|
198
|
+
}
|
|
199
|
+
function readDurableRecord(agentRoot) {
|
|
200
|
+
const candidates = [rollbackPath(agentRoot), path.join(agentRoot, SANCTUARY_BUNDLE_COMMITTING_FILE)].filter((candidate) => fs.existsSync(candidate));
|
|
201
|
+
if (candidates.length === 0)
|
|
202
|
+
return null;
|
|
203
|
+
if (candidates.length !== 1)
|
|
204
|
+
throw new Error("Sanctuary bundle rollback record state is ambiguous");
|
|
205
|
+
const [filePath] = candidates;
|
|
206
|
+
const stat = fs.lstatSync(filePath);
|
|
207
|
+
if (stat.isSymbolicLink())
|
|
208
|
+
throw new Error("Sanctuary bundle rollback record must not be a symlink");
|
|
209
|
+
if (!stat.isFile() || (stat.mode & 0o777) !== 0o600)
|
|
210
|
+
throw new Error("Sanctuary bundle rollback record must be a mode-0600 regular file");
|
|
211
|
+
let value;
|
|
212
|
+
try {
|
|
213
|
+
value = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
throw new Error("Sanctuary bundle rollback record is invalid JSON");
|
|
217
|
+
}
|
|
218
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
219
|
+
throw new Error("Sanctuary bundle rollback record is invalid");
|
|
220
|
+
const record = value;
|
|
221
|
+
if (record.schemaVersion !== 1 || typeof record.rollbackImageId !== "string" || !/^sha256:[0-9a-f]{64}$/u.test(record.rollbackImageId) || typeof record.targetImageId !== "string" || !/^sha256:[0-9a-f]{64}$/u.test(record.targetImageId) || record.targetImageId === record.rollbackImageId || typeof record.snapshotDigest !== "string" || !/^[0-9a-f]{64}$/u.test(record.snapshotDigest) || !Array.isArray(record.files) || !Array.isArray(record.directories))
|
|
222
|
+
throw new Error("Sanctuary bundle rollback record is invalid");
|
|
223
|
+
const fileRelatives = record.files.map((file) => file?.relative);
|
|
224
|
+
const directoryRelatives = record.directories.map((directory) => directory?.relative);
|
|
225
|
+
const expectedDirectories = snapshotDirectoryRelatives();
|
|
226
|
+
if (JSON.stringify(fileRelatives) !== JSON.stringify(SNAPSHOT_RELATIVES) || JSON.stringify(directoryRelatives) !== JSON.stringify(expectedDirectories))
|
|
227
|
+
throw new Error("Sanctuary bundle rollback record paths are invalid");
|
|
228
|
+
for (const relative of SNAPSHOT_RELATIVES)
|
|
229
|
+
validateDestination(agentRoot, relative);
|
|
230
|
+
const validMode = (mode) => mode === null || (Number.isInteger(mode) && mode >= 0 && mode <= 0o777);
|
|
231
|
+
const files = record.files.map((file) => {
|
|
232
|
+
if (!file || typeof file.relative !== "string" || !validMode(file.mode) || (file.contentBase64 !== null && typeof file.contentBase64 !== "string") || (file.contentBase64 === null) !== (file.mode === null))
|
|
233
|
+
throw new Error("Sanctuary bundle rollback record file is invalid");
|
|
234
|
+
const content = file.contentBase64 === null ? null : Buffer.from(file.contentBase64, "base64");
|
|
235
|
+
if (file.contentBase64 !== null && (!content || content.toString("base64") !== file.contentBase64))
|
|
236
|
+
throw new Error("Sanctuary bundle rollback record content is invalid");
|
|
237
|
+
return { path: path.join(agentRoot, file.relative), content, mode: file.mode };
|
|
238
|
+
});
|
|
239
|
+
const directories = record.directories.map((directory) => {
|
|
240
|
+
if (!directory || typeof directory.relative !== "string" || !validMode(directory.mode))
|
|
241
|
+
throw new Error("Sanctuary bundle rollback record directory is invalid");
|
|
242
|
+
return { path: path.join(agentRoot, directory.relative), mode: directory.mode };
|
|
243
|
+
});
|
|
244
|
+
const payload = { schemaVersion: record.schemaVersion, rollbackImageId: record.rollbackImageId, targetImageId: record.targetImageId, files: record.files, directories: record.directories };
|
|
245
|
+
if ((0, node_crypto_1.createHash)("sha256").update(JSON.stringify(payload)).digest("hex") !== record.snapshotDigest)
|
|
246
|
+
throw new Error("Sanctuary bundle rollback record digest is invalid");
|
|
247
|
+
return { snapshot: { files, directories }, rollbackImageId: record.rollbackImageId, targetImageId: record.targetImageId, filePath, state: filePath === rollbackPath(agentRoot) ? "rollback" : "committing" };
|
|
248
|
+
}
|
|
249
|
+
function removeRollbackRecord(agentRoot, filePath) {
|
|
250
|
+
fs.unlinkSync(filePath);
|
|
251
|
+
const root = fs.openSync(agentRoot, fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW);
|
|
252
|
+
try {
|
|
253
|
+
fs.fsyncSync(root);
|
|
254
|
+
}
|
|
255
|
+
finally {
|
|
256
|
+
fs.closeSync(root);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function validateAgentRoot(agentRoot) {
|
|
260
|
+
if (!path.isAbsolute(agentRoot))
|
|
261
|
+
throw new Error("agent root must be an absolute path");
|
|
262
|
+
const stat = fs.lstatSync(agentRoot);
|
|
263
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
264
|
+
throw new Error("agent root must be a real directory");
|
|
265
|
+
}
|
|
266
|
+
function inspectSanctuaryPackageManagedBundleRollback(agentRoot) {
|
|
267
|
+
validateAgentRoot(agentRoot);
|
|
268
|
+
const record = readDurableRecord(agentRoot);
|
|
269
|
+
return record ? { rollbackImageId: record.rollbackImageId, targetImageId: record.targetImageId, state: record.state } : null;
|
|
270
|
+
}
|
|
271
|
+
function rollbackSanctuaryPackageManagedBundle(agentRoot, options = {}) {
|
|
272
|
+
validateAgentRoot(agentRoot);
|
|
273
|
+
const record = readDurableRecord(agentRoot);
|
|
274
|
+
if (!record)
|
|
275
|
+
return false;
|
|
276
|
+
if (record.state === "committing")
|
|
277
|
+
throw new Error("Sanctuary bundle commit is pending and cannot be rolled back");
|
|
278
|
+
restoreMigrationSnapshot(record.snapshot);
|
|
279
|
+
if (!options.retainRecord)
|
|
280
|
+
removeRollbackRecord(agentRoot, record.filePath);
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
function commitSanctuaryPackageManagedBundle(agentRoot) {
|
|
284
|
+
validateAgentRoot(agentRoot);
|
|
285
|
+
const record = readDurableRecord(agentRoot);
|
|
286
|
+
if (!record)
|
|
287
|
+
return false;
|
|
288
|
+
const committingPath = path.join(agentRoot, SANCTUARY_BUNDLE_COMMITTING_FILE);
|
|
289
|
+
if (record.filePath !== committingPath) {
|
|
290
|
+
fs.renameSync(record.filePath, committingPath);
|
|
291
|
+
const root = fs.openSync(agentRoot, fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW);
|
|
292
|
+
try {
|
|
293
|
+
fs.fsyncSync(root);
|
|
294
|
+
}
|
|
295
|
+
finally {
|
|
296
|
+
fs.closeSync(root);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
removeRollbackRecord(agentRoot, committingPath);
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
function restoreMigrationSnapshot(snapshot) {
|
|
303
|
+
for (const file of snapshot.files) {
|
|
304
|
+
cleanupInterruptedWrites(file.path);
|
|
305
|
+
if (file.content === null) {
|
|
306
|
+
fs.rmSync(file.path, { force: true });
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
writeAtomic(file.path, file.content);
|
|
310
|
+
fs.chmodSync(file.path, file.mode);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
for (const directory of [...snapshot.directories].reverse()) {
|
|
314
|
+
if (directory.mode === null) {
|
|
315
|
+
if (!fs.existsSync(directory.path))
|
|
316
|
+
continue;
|
|
317
|
+
if (fs.readdirSync(directory.path).length === 0)
|
|
318
|
+
fs.rmdirSync(directory.path);
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
fs.chmodSync(directory.path, directory.mode);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function migrateSanctuaryPackageManagedBundle(input) {
|
|
326
|
+
(0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.sanctuary_bundle_migration_start", message: "starting Sanctuary package-managed bundle migration", meta: { agentRoot: input.agentRoot } });
|
|
327
|
+
let snapshot = null;
|
|
328
|
+
try {
|
|
329
|
+
if (!path.isAbsolute(input.packageRoot) || !path.isAbsolute(input.agentRoot) || input.packageRoot === input.agentRoot)
|
|
330
|
+
throw new Error("package and agent roots must be distinct absolute paths");
|
|
331
|
+
const packageStat = fs.lstatSync(input.packageRoot);
|
|
332
|
+
const agentStat = fs.lstatSync(input.agentRoot);
|
|
333
|
+
if (packageStat.isSymbolicLink() || !packageStat.isDirectory())
|
|
334
|
+
throw new Error("package root must be a real directory");
|
|
335
|
+
if (agentStat.isSymbolicLink() || !agentStat.isDirectory())
|
|
336
|
+
throw new Error("agent root must be a real directory");
|
|
337
|
+
if (input.retainRollback && (!input.rollbackImageId || !/^sha256:[0-9a-f]{64}$/u.test(input.rollbackImageId) || !input.targetImageId || !/^sha256:[0-9a-f]{64}$/u.test(input.targetImageId) || input.targetImageId === input.rollbackImageId))
|
|
338
|
+
throw new Error("retained rollback requires distinct exact rollback and target image IDs");
|
|
339
|
+
const existingRollback = rollbackPath(input.agentRoot);
|
|
340
|
+
if (readDurableRecord(input.agentRoot))
|
|
341
|
+
throw new Error("Sanctuary bundle rollback is pending; rollback or commit it before another migration");
|
|
342
|
+
for (const relative of exports.SANCTUARY_PACKAGE_MANAGED_FILES) {
|
|
343
|
+
requirePlainFile(path.join(input.packageRoot, relative), `package-managed source ${relative}`);
|
|
344
|
+
validateDestination(input.agentRoot, relative);
|
|
345
|
+
}
|
|
346
|
+
requirePlainFile(path.join(input.packageRoot, "bundle-meta.json"), "packaged bundle-meta.json");
|
|
347
|
+
validateDestination(input.agentRoot, "bundle-meta.json");
|
|
348
|
+
const packagedMeta = readObject(path.join(input.packageRoot, "bundle-meta.json"));
|
|
349
|
+
for (const field of BUNDLE_META_VERSION_FIELDS)
|
|
350
|
+
if (!(field in packagedMeta))
|
|
351
|
+
throw new Error(`packaged bundle-meta.json is missing ${field}`);
|
|
352
|
+
const packagedPolicy = (0, steward_policy_1.readStewardPolicy)(input.packageRoot);
|
|
353
|
+
if (Object.keys(packagedPolicy.routineActionGrants).length > 0) {
|
|
354
|
+
throw new Error("packaged steward policy must not carry routine action grants; authorize them through an authenticated owner session");
|
|
355
|
+
}
|
|
356
|
+
snapshot = captureMigrationSnapshot(input.agentRoot);
|
|
357
|
+
if (input.retainRollback) {
|
|
358
|
+
cleanupInterruptedWrites(existingRollback);
|
|
359
|
+
writeAtomic(existingRollback, `${JSON.stringify(durableSnapshot(input.agentRoot, snapshot, input.rollbackImageId, input.targetImageId))}\n`);
|
|
360
|
+
}
|
|
361
|
+
let managedFilesUpdated = 0;
|
|
362
|
+
for (const relative of exports.SANCTUARY_PACKAGE_MANAGED_FILES) {
|
|
363
|
+
const source = fs.readFileSync(path.join(input.packageRoot, relative), "utf8");
|
|
364
|
+
const destination = path.join(input.agentRoot, relative);
|
|
365
|
+
ensureSafeParent(input.agentRoot, relative);
|
|
366
|
+
if (fs.existsSync(destination) && fs.readFileSync(destination, "utf8") === source && (fs.statSync(destination).mode & 0o777) === 0o600)
|
|
367
|
+
continue;
|
|
368
|
+
writeAtomic(destination, source);
|
|
369
|
+
managedFilesUpdated += 1;
|
|
370
|
+
}
|
|
371
|
+
const metaPath = path.join(input.agentRoot, "bundle-meta.json");
|
|
372
|
+
const currentMeta = fs.existsSync(metaPath) ? readObject(metaPath) : {};
|
|
373
|
+
const nextMeta = { ...currentMeta };
|
|
374
|
+
for (const field of BUNDLE_META_VERSION_FIELDS)
|
|
375
|
+
nextMeta[field] = packagedMeta[field];
|
|
376
|
+
const nextMetaText = `${JSON.stringify(nextMeta, null, 2)}\n`;
|
|
377
|
+
if (!fs.existsSync(metaPath) || fs.readFileSync(metaPath, "utf8") !== nextMetaText)
|
|
378
|
+
writeAtomic(metaPath, nextMetaText);
|
|
379
|
+
const result = { managedFilesUpdated };
|
|
380
|
+
(0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.sanctuary_bundle_migration_end", message: "completed Sanctuary package-managed bundle migration", meta: result });
|
|
381
|
+
return result;
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
let failure = error;
|
|
385
|
+
if (snapshot) {
|
|
386
|
+
try {
|
|
387
|
+
restoreMigrationSnapshot(snapshot);
|
|
388
|
+
if (fs.existsSync(rollbackPath(input.agentRoot)))
|
|
389
|
+
removeRollbackRecord(input.agentRoot, rollbackPath(input.agentRoot));
|
|
390
|
+
}
|
|
391
|
+
catch (restoreError) {
|
|
392
|
+
failure = new AggregateError([error, restoreError], "Sanctuary bundle migration and rollback both failed");
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
(0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.sanctuary_bundle_migration_error", level: "error", message: "Sanctuary package-managed bundle migration failed", meta: { error: String(failure), agentRoot: input.agentRoot } });
|
|
396
|
+
throw failure;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
@@ -73,6 +73,9 @@ function safeEqual(left, right) {
|
|
|
73
73
|
return false;
|
|
74
74
|
return (0, node_crypto_1.timingSafeEqual)(Buffer.from(left, "hex"), Buffer.from(right, "hex"));
|
|
75
75
|
}
|
|
76
|
+
function isSupercronicProcess(pid, deps) {
|
|
77
|
+
return deps.readLink(`/proc/${pid}/exe`) === SUPERCRONIC && deps.readFile(`/proc/${pid}/cmdline`) === SUPERCRONIC_CMDLINE;
|
|
78
|
+
}
|
|
76
79
|
function defaultSanctuarySchedulerOriginDeps(agentRoot = (0, identity_1.getAgentRoot)("sanctuary"), markerReader = () => (0, sanctuary_acceptance_marker_1.readSanctuaryAcceptanceMarker)("sanctuary")) {
|
|
77
80
|
return {
|
|
78
81
|
platform: process.platform, pid: process.pid, ppid: process.ppid, now: () => new Date(),
|
|
@@ -84,10 +87,10 @@ function createSanctuarySchedulerFireCommand(command, deps = defaultSanctuarySch
|
|
|
84
87
|
if (command.agent !== "sanctuary" || command.habitName !== "sanctuary-health" || command.trigger !== "cron" || deps.platform !== "linux")
|
|
85
88
|
return null;
|
|
86
89
|
try {
|
|
87
|
-
if (deps.readLink(`/proc/${deps.ppid}/exe`) !== SUPERCRONIC || deps.readFile(`/proc/${deps.ppid}/cmdline`) !== SUPERCRONIC_CMDLINE)
|
|
88
|
-
return null;
|
|
89
90
|
const invocation = processIdentity(deps.readFile(`/proc/${deps.pid}/stat`));
|
|
90
91
|
const parent = processIdentity(deps.readFile(`/proc/${deps.ppid}/stat`));
|
|
92
|
+
if (!isSupercronicProcess(deps.ppid, deps) && !isSupercronicProcess(parent.parentPid, deps))
|
|
93
|
+
return null;
|
|
91
94
|
const marker = deps.marker();
|
|
92
95
|
if (invocation.parentPid !== deps.ppid)
|
|
93
96
|
return null;
|
|
@@ -113,9 +116,12 @@ function verifySanctuarySchedulerFireCommand(command, deps) {
|
|
|
113
116
|
throw new Error("scheduler fire authentication failed");
|
|
114
117
|
const invocation = processIdentity(deps.readFile(`/proc/${command.invocationPid}/stat`));
|
|
115
118
|
const parent = processIdentity(deps.readFile(`/proc/${command.parentPid}/stat`));
|
|
116
|
-
if (
|
|
117
|
-
|| parent.startTime !== command.parentStartTime
|
|
118
|
-
|
|
119
|
+
if (invocation.parentPid !== command.parentPid || invocation.startTime !== command.invocationStartTime
|
|
120
|
+
|| parent.startTime !== command.parentStartTime)
|
|
121
|
+
throw new Error("scheduler fire ancestry is invalid");
|
|
122
|
+
if (command.parentPid !== deps.childPid && parent.parentPid !== deps.childPid)
|
|
123
|
+
throw new Error("scheduler fire ancestry is invalid");
|
|
124
|
+
if (command.parentPid === deps.childPid ? !isSupercronicProcess(command.parentPid, deps) : !isSupercronicProcess(parent.parentPid, deps))
|
|
119
125
|
throw new Error("scheduler fire ancestry is invalid");
|
|
120
126
|
(0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.sanctuary_scheduler_origin_authenticated", message: "Sanctuary scheduler origin authenticated", meta: { schedulerRunId: command.schedulerRunId, slot: command.slot } });
|
|
121
127
|
return {
|