chainlesschain 0.166.24 → 0.166.26
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/README.md +52 -3
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-DR-MWcZc.js → AIOps-Z8_dnLd8.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DT8B7sEb.js → ActionButton-C4bKCISV.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-asjh3dQJ.js → Analytics-GztAv2jy.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DivQ491i.js → AppLayout-DEqNcuVx.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-DPH2uUmy.js → Artifacts-D-R4Ehap.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-Bp2nLyc-.js → Audit-mzzdTIlM.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-CHS28khl.js → BackgroundAgents-DS2QupIC.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CKeaFSfz.js → Backup-CQSirC39.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CxwM9BAH.js → BaseInput-Bhuc-eOV.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DYLDwaCv.js → Chat-B-RLVGnt.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-DdBJDBP0.js → ChatBubbleRenderer-CBbMoVuE.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-uYTWOB8j.js → Checkbox-DxwgBryW.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CvYa_dwT.js → Codegen-Cwrm-7nh.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CanMAX4m.js → Col-DrSCT-nF.js} +1 -1
- package/src/assets/web-panel/assets/{Community-B4WaDtHw.js → Community-DY2D9WNs.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-K93Uof_3.js → Compact-o-tV0URw.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CKeDLgUW.js → Compliance-Bndh6S8f.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-P93grAeB.js → Cowork-CSNQODak.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-BSHnMtDn.js → Cron-CTgxRHdy.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-epAOpUVJ.js → Crosschain-BJoGQzDI.js} +1 -1
- package/src/assets/web-panel/assets/{DID-ByjXfP7t.js → DID-V0Ztm8ne.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-QztTU8tb.js → Dashboard-B-38THOj.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DhUc9yhN.js → Dropdown-B3FdOwMO.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-C2sLhJJN.js → EmailListRenderer-YQDz4mUF.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CdfbWNDn.js → FamilyGuardDashboard-DYE45IF9.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CcF_R7NW.js → Federation-DjBFjdRZ.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Cm_X2IBk.js → FormItemContext-CVTXEa0R.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DUi4NWlb.js → GenericCardRenderer-B6Tkke8X.js} +1 -1
- package/src/assets/web-panel/assets/{Git-4Sq5FjBt.js → Git-CD0hyAzT.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-Dx2_v4V6.js → Governance-CWOQQJr8.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-7Vr1xekR.js → Inference-lHYwUk-C.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-Cnd2XOzf.js → KnowledgeGraph-Cledxv7t.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-B66QPVf-.js → Logs-D45mt-rM.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-PLsEvhly.js → MarkdownRenderer-DJjPaOnM.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-BMIK9BmC.js → Marketplace-Yy30UZPw.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BDbyp3J_.js → McpTools-Cil5l-bh.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-CJdzfZGi.js → Memory-DdJcnyqy.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-C2TH5Y3-.js → MobileBridge-IrzFYHEv.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-D1dYXkvV.js → MobileProjects-D8q64LMd.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-B-4A1I1j.js → Mtc-CpdTJzW7.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-Bgcc9UOl.js → MtcAudit-C7Tvw2m6.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-DqOe-etP.js → Multisig-Dmj9F5Kr.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-7VnhzE_6.js → NLProgramming-C9Xchob5.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BxawpeUQ.js → Notes-Bzq6Yry5.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-Cr08G2aE.js → NotificationSettings-DxG9eKb4.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-Ce8bEGlK.js → OrderTableRenderer-BS_IYoCe.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BC7lveGv.js → Organization-kazsRvcq.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-CakOW-G0.js → Overflow-i4wP-hwn.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Cf8CaOBZ.js → P2P-CfGeU3h-.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Dc7b98W9.js → PdhVaultBrowser-CS-VhVUx.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-DGJyMEfS.js → Permissions-DnEHXs0I.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-DNIwSdwu.js → PersonalDataHub-DeD82qke.js} +6 -6
- package/src/assets/web-panel/assets/{Pipeline-5B1S12wa.js → Pipeline-CMGGLgBY.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CMB6Ke5t.js → Privacy-CvHoFv32.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-D5PqTFiT.js → ProjectInit-DnJapCc1.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CbKNdSjV.js → ProjectSettings-CqVdv3-g.js} +2 -2
- package/src/assets/web-panel/assets/Projects-B_o8vcs_.js +1 -0
- package/src/assets/web-panel/assets/{Providers-CJF5_-Gs.js → Providers-CPKseoDW.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CT6iEAdi.js → QrScannerModal-_iBfKbPQ.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-Ci0h9mJ_.js → QuickAsk-CMrlZ69a.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Da5mobZu.js → Recommend-BwDtSZ0l.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-BaCY8eey.js → RemoteSession-DomUAtn4.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-CaITfTsI.js → Reputation-Cuu2GCHr.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DtUDamyP.js → Row-C7GVts31.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DX27gFpg.js → RssFeed-DRKvgEog.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DFL69osN.js → Search-BGI9wOeA.js} +1 -1
- package/src/assets/web-panel/assets/{Security-BqGeoWv-.js → Security-BlLaHy_L.js} +3 -3
- package/src/assets/web-panel/assets/{Services-3z1WnEAc.js → Services-DFqZbjmX.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-C_OJQQ9Z.js → Skeleton-BCGxdooP.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BY7aT6Mo.js → Skills-CwFKgcmm.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-BZv7DSaD.js → Sla-CiXXh9eS.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-WSx2CLqO.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-B6D7STlI.js → SyncSettings-BBIqQOtW.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Co_FCZAO.js → Tasks-D1BGLjO1.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CnY-9fgz.js → Templates-CUIl9axA.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-B71yXfZ8.js → Tenant-BFZJ0hNz.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-Dd_P7awo.js → Terminal-Cw-3eYVn.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-CvIEMyli.js → TimelineRenderer-bOVPB56L.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DknFBGOi.js → Tokens-Czfyq2VY.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-D_4nJONB.js → Trigger-C-o4uJY0.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BNl21fa7.js → Trust-BKq_S6iF.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CFrCI7uK.js → UkeySign-fKlWUvMx.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BVfY6JLg.js → VideoEditing-DB5MLuS7.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-Cj9CAK0J.js → Wallet-WGXBn7IS.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DcqZlzA3.js → WebAuthn-B8TC4EMF.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-Dfr_dMld.js → WorkflowEditor-DLY--KmM.js} +1 -1
- package/src/assets/web-panel/assets/{chat-Yv6UF0AD.js → chat-Crw3XQaA.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CDW_-1fs.js → colors-CxhWauPV.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BYOQvd-6.js → compact-item-Cp3ojtk5.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BCGO0BiP.js → createContext-CRuRXjSo.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CKAQY2zn.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DY-70cfQ.js → hasIn-XGW474dj.js} +1 -1
- package/src/assets/web-panel/assets/{index-B9ZogJJM.js → index-38TRdZb6.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBs2mv5M.js → index-B45Vkh3T.js} +1 -1
- package/src/assets/web-panel/assets/{index-CCwWp3mT.js → index-B5fJS-kn.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUo27q9J.js → index-BElgeKJ0.js} +3 -3
- package/src/assets/web-panel/assets/{index-CFa2fPCk.js → index-BUWZ57T4.js} +1 -1
- package/src/assets/web-panel/assets/{index-DCtsjhhT.js → index-BVmSfIkJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-NaCESahy.js → index-BW028Pwn.js} +1 -1
- package/src/assets/web-panel/assets/{index-CaT8trvf.js → index-BXnNbbwa.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRFC2Qt0.js → index-BYkjs7eP.js} +1 -1
- package/src/assets/web-panel/assets/{index-Br3wQLVY.js → index-BetOkRLs.js} +1 -1
- package/src/assets/web-panel/assets/{index-DuIPq5MX.js → index-BuVA26SD.js} +1 -1
- package/src/assets/web-panel/assets/{index-C4UJA2ju.js → index-C73pqyDb.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvK4fjkk.js → index-C8UOVaUH.js} +1 -1
- package/src/assets/web-panel/assets/{index-hZ0Eyrrw.js → index-CDIpH_8Q.js} +1 -1
- package/src/assets/web-panel/assets/{index-D--TEyDj.js → index-CPFam-me.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUP-A2_h.js → index-CTE32qol.js} +1 -1
- package/src/assets/web-panel/assets/{index-CnZNbcux.js → index-CdlfrcC-.js} +1 -1
- package/src/assets/web-panel/assets/index-Cetfwq8j.js +1 -0
- package/src/assets/web-panel/assets/{index-C40UdtlS.js → index-Ctu98x57.js} +1 -1
- package/src/assets/web-panel/assets/{index-B9m5QAMg.js → index-DBcBsJYg.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXlEx4UH.js → index-DHNixOg_.js} +1 -1
- package/src/assets/web-panel/assets/index-DOpcRO2-.js +1 -0
- package/src/assets/web-panel/assets/{index-Dg-AQYDf.js → index-DUSVsOGS.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzeEEazr.js → index-De2FN0xN.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUDoRk9r.js → index-DfPLIA-f.js} +1 -1
- package/src/assets/web-panel/assets/{index-Y9akzgFO.js → index-DhomnuaB.js} +1 -1
- package/src/assets/web-panel/assets/{index-366wfQBU.js → index-DoUC5_ry.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bb9nyts1.js → index-Dp2nC5On.js} +1 -1
- package/src/assets/web-panel/assets/{index-CIyHxV4u.js → index-DxOtBFVy.js} +1 -1
- package/src/assets/web-panel/assets/{index-BFv1hzF6.js → index-E-RSfImO.js} +1 -1
- package/src/assets/web-panel/assets/{index-DLOf7BSz.js → index-J_08R33H.js} +1 -1
- package/src/assets/web-panel/assets/{index-RjIMPG0u.js → index-QqAuH7-S.js} +1 -1
- package/src/assets/web-panel/assets/{index-CB_Iw73G.js → index-W8_0yAi_.js} +1 -1
- package/src/assets/web-panel/assets/{index-DM5c4pc-.js → index-WvNZXbIO.js} +1 -1
- package/src/assets/web-panel/assets/{index-DT_qVaaV.js → index-Yq3bFbYX.js} +1 -1
- package/src/assets/web-panel/assets/{index-NYVBUAVN.js → index-jrIcbPUa.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cy2NE6AO.js → index-nJR64pA_.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bf_Va0S-.js → index-qzmihx1d.js} +1 -1
- package/src/assets/web-panel/assets/{index-BayRTE5w.js → index-rsACoVT9.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CG99CcR4.js → initDefaultProps-DAu-hgme.js} +1 -1
- package/src/assets/web-panel/assets/{motion-Z7MH0_0G.js → motion-Db8yk0Hu.js} +1 -1
- package/src/assets/web-panel/assets/{move-BX67lOdq.js → move-Cii2jOin.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-CmAbG1fE.js → mtc-parser-CnZBDkyZ.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DiU5tI1_.js → omit-Buzt1FTL.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BGfvLrYL.js → pickAttrs-CpZ_NmE6.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CggLEgaJ.js → placementArrow-mBrx3dYm.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-t3nlghPZ.js → responsiveObserve-DC_QzljB.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BkV4dL9s.js → slide-DT_UVED-.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-qec1PUoC.js → statusUtils-78t6eNLV.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-ChjX6T8D.js → styleChecker-CV_T1ULL.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-B1jLoZcM.js → useFlexGapSupport-s8edGuXD.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-C3X53s4E.js → useFs-Pk9dH2HC.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DOvbbnkZ.js → usePersonalDataHub-B_GqAm8c.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-Ie0TEVFT.js → vnode-Cxyss24q.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CRUMVS8l.js → zoom-0UY3cOuc.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/evolution-knowledge.js +40 -1
- package/src/commands/serve.js +11 -0
- package/src/data/changelog.json +29 -3
- package/src/generated/cc-agent-protocol.schema.json +3 -1
- package/src/lib/app-server/server.js +49 -0
- package/src/lib/app-server/websocket-transport.js +13 -0
- package/src/lib/evolution/agent-model-projection.js +252 -32
- package/src/lib/evolution/evidence-backed-wiki-maintainer.js +22 -9
- package/src/lib/evolution/evolution-artifact-ports.js +3 -0
- package/src/lib/evolution/evolution-deployment-loader.js +77 -0
- package/src/lib/evolution/evolution-evidence-projector.js +36 -5
- package/src/lib/evolution/evolution-ledger-ports.js +210 -18
- package/src/lib/evolution/governed-knowledge-dependency-inventory.js +392 -0
- package/src/lib/evolution/governed-knowledge-dependency-ledger-executor.js +123 -0
- package/src/lib/evolution/governed-knowledge-quarantine-release.js +1013 -0
- package/src/lib/evolution/governed-knowledge-revocation-host.js +59 -0
- package/src/lib/evolution/governed-knowledge-skill-rollback.js +62 -27
- package/src/lib/evolution/governed-knowledge-sync.js +164 -1
- package/src/lib/evolution/governed-knowledge-wiki-tombstone.js +88 -34
- package/src/lib/evolution/knowledge-wiki-source-admission.js +160 -1
- package/src/lib/evolution/knowledge-wiki-source-provenance.js +1 -0
- package/src/lib/evolution/skill-candidate-registry.js +21 -3
- package/src/lib/evolution/skill-release-registry.js +143 -0
- package/src/lib/evolution/wiki-maintainer-ledger-adapter.js +61 -0
- package/src/lib/evolution/wiki-target-tombstone-revision.js +15 -7
- package/src/lib/read-file-loop-guard.js +222 -0
- package/src/lib/read-file-page.js +55 -2
- package/src/repl/agent-repl.js +6 -1
- package/src/runtime/agent-core.js +177 -57
- package/src/assets/web-panel/assets/Projects-ccXaHzYL.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-6lJKLhCn.js +0 -1
- package/src/assets/web-panel/assets/devWarning-H-kXSuf-.js +0 -1
- package/src/assets/web-panel/assets/index-B0o4MeNK.js +0 -1
- package/src/assets/web-panel/assets/index-BVafOL_I.js +0 -1
|
@@ -0,0 +1,1013 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { types as utilTypes } from "node:util";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
EVOLUTION_DURABLE_ARTIFACT_RECORD_SCHEMA,
|
|
6
|
+
isEvolutionLedgerArtifactResolver,
|
|
7
|
+
} from "./evolution-artifact-ports.js";
|
|
8
|
+
import {
|
|
9
|
+
EVOLUTION_ARTIFACT_RESOLUTION_SCHEMA,
|
|
10
|
+
EVOLUTION_LEDGER_DOMAIN_EVENT_SCHEMA,
|
|
11
|
+
} from "./evolution-ledger.js";
|
|
12
|
+
import {
|
|
13
|
+
WIKI_REVISION_SCHEMA,
|
|
14
|
+
digestWikiState,
|
|
15
|
+
} from "./evidence-backed-wiki-maintainer.js";
|
|
16
|
+
import { captureWikiRevisionReader } from "./wiki-maintainer-ledger-adapter.js";
|
|
17
|
+
import { verifyWikiRevision } from "./wiki-revision-protocol.js";
|
|
18
|
+
|
|
19
|
+
export const GOVERNED_KNOWLEDGE_QUARANTINE_RELEASE_SCHEMA =
|
|
20
|
+
"chainlesschain.governed-knowledge-quarantine-release/v1";
|
|
21
|
+
export const GOVERNED_KNOWLEDGE_QUARANTINE_RELEASE_REQUEST_SCHEMA =
|
|
22
|
+
"chainlesschain.governed-knowledge-quarantine-release-request/v1";
|
|
23
|
+
export const GOVERNED_KNOWLEDGE_QUARANTINE_RELEASE_EVENT_TYPE =
|
|
24
|
+
"knowledge.quarantine-release.committed";
|
|
25
|
+
export const GOVERNED_KNOWLEDGE_QUARANTINE_RELEASE_CORRUPT_CODE =
|
|
26
|
+
"CC_GOVERNED_KNOWLEDGE_QUARANTINE_RELEASE_CORRUPT";
|
|
27
|
+
|
|
28
|
+
const ARTIFACT_TYPE = "governed-knowledge-quarantine-release";
|
|
29
|
+
const DIGEST = /^sha256:[a-f0-9]{64}$/u;
|
|
30
|
+
const ID = /^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,255}$/u;
|
|
31
|
+
const RELEASES = new WeakSet();
|
|
32
|
+
const DECISIONS = new WeakMap();
|
|
33
|
+
const DEPENDENCY_KEYS = new Set(["kind", "digest", "disposition"]);
|
|
34
|
+
const DESCRIPTOR_KEYS = new Set([
|
|
35
|
+
"authorityId",
|
|
36
|
+
"handlerArtifactDigest",
|
|
37
|
+
"revision",
|
|
38
|
+
]);
|
|
39
|
+
const VERIFICATION_KEYS = new Set([
|
|
40
|
+
"authenticated",
|
|
41
|
+
"durable",
|
|
42
|
+
"authorityId",
|
|
43
|
+
"authorityRevision",
|
|
44
|
+
"handlerArtifactDigest",
|
|
45
|
+
"requestDigest",
|
|
46
|
+
"receiptDigest",
|
|
47
|
+
"decision",
|
|
48
|
+
"verifiedAt",
|
|
49
|
+
]);
|
|
50
|
+
const REQUEST_KEYS = new Set([
|
|
51
|
+
"schema",
|
|
52
|
+
"tenantId",
|
|
53
|
+
"operationDigest",
|
|
54
|
+
"knowledgeId",
|
|
55
|
+
"contentDigest",
|
|
56
|
+
"dependencies",
|
|
57
|
+
"preparedRecordDigest",
|
|
58
|
+
"settlementRecordDigest",
|
|
59
|
+
"reason",
|
|
60
|
+
"requestDigest",
|
|
61
|
+
]);
|
|
62
|
+
const RECORD_KEYS = new Set([
|
|
63
|
+
"schema",
|
|
64
|
+
"tenantId",
|
|
65
|
+
"operationDigest",
|
|
66
|
+
"knowledgeId",
|
|
67
|
+
"contentDigest",
|
|
68
|
+
"dependencies",
|
|
69
|
+
"preparedRecordDigest",
|
|
70
|
+
"settlementRecordDigest",
|
|
71
|
+
"reason",
|
|
72
|
+
"requestDigest",
|
|
73
|
+
"approval",
|
|
74
|
+
"evaluation",
|
|
75
|
+
"committedAt",
|
|
76
|
+
"recordDigest",
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
function fail(message) {
|
|
80
|
+
const error = new Error(message);
|
|
81
|
+
error.code = GOVERNED_KNOWLEDGE_QUARANTINE_RELEASE_CORRUPT_CODE;
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function exact(value, keys, label) {
|
|
86
|
+
if (
|
|
87
|
+
!value ||
|
|
88
|
+
typeof value !== "object" ||
|
|
89
|
+
Array.isArray(value) ||
|
|
90
|
+
utilTypes.isProxy(value) ||
|
|
91
|
+
![Object.prototype, null].includes(Object.getPrototypeOf(value))
|
|
92
|
+
) {
|
|
93
|
+
throw new TypeError(`${label} must be a plain object`);
|
|
94
|
+
}
|
|
95
|
+
const actual = Reflect.ownKeys(value);
|
|
96
|
+
if (
|
|
97
|
+
actual.length !== keys.size ||
|
|
98
|
+
actual.some((key) => typeof key !== "string" || !keys.has(key)) ||
|
|
99
|
+
actual.some(
|
|
100
|
+
(key) =>
|
|
101
|
+
!Object.hasOwn(Object.getOwnPropertyDescriptor(value, key), "value"),
|
|
102
|
+
)
|
|
103
|
+
) {
|
|
104
|
+
throw new TypeError(`${label} has an invalid shape`);
|
|
105
|
+
}
|
|
106
|
+
return value;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function identifier(value, label) {
|
|
110
|
+
if (typeof value !== "string" || !ID.test(value)) {
|
|
111
|
+
throw new TypeError(`${label} is invalid`);
|
|
112
|
+
}
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function canonical(value) {
|
|
117
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
118
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
119
|
+
return `{${Object.keys(value)
|
|
120
|
+
.sort()
|
|
121
|
+
.map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
|
|
122
|
+
.join(",")}}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function hash(domain, value) {
|
|
126
|
+
return `sha256:${createHash("sha256")
|
|
127
|
+
.update(domain)
|
|
128
|
+
.update("\0")
|
|
129
|
+
.update(canonical(value))
|
|
130
|
+
.digest("hex")}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function wikiHash(value) {
|
|
134
|
+
return `sha256:${createHash("sha256").update(canonical(value)).digest("hex")}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function clone(value) {
|
|
138
|
+
return structuredClone(value);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function freeze(value) {
|
|
142
|
+
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
143
|
+
Object.freeze(value);
|
|
144
|
+
for (const child of Object.values(value)) freeze(child);
|
|
145
|
+
}
|
|
146
|
+
return value;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function capture(owner, method, label) {
|
|
150
|
+
if (!owner || typeof owner !== "object" || utilTypes.isProxy(owner)) {
|
|
151
|
+
throw new TypeError(`${label}.${method}() is required`);
|
|
152
|
+
}
|
|
153
|
+
const implementation = owner[method];
|
|
154
|
+
if (
|
|
155
|
+
typeof implementation !== "function" ||
|
|
156
|
+
utilTypes.isProxy(implementation)
|
|
157
|
+
) {
|
|
158
|
+
throw new TypeError(`${label}.${method}() must be a non-proxy function`);
|
|
159
|
+
}
|
|
160
|
+
return (...args) => Reflect.apply(implementation, owner, args);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function authorityDescriptor(input, label) {
|
|
164
|
+
exact(input, DESCRIPTOR_KEYS, `${label} descriptor`);
|
|
165
|
+
if (
|
|
166
|
+
!Number.isSafeInteger(input.revision) ||
|
|
167
|
+
input.revision < 1 ||
|
|
168
|
+
!DIGEST.test(input.handlerArtifactDigest ?? "")
|
|
169
|
+
) {
|
|
170
|
+
throw new TypeError(`${label} descriptor is invalid`);
|
|
171
|
+
}
|
|
172
|
+
return freeze({
|
|
173
|
+
authorityId: identifier(input.authorityId, `${label} authorityId`),
|
|
174
|
+
revision: input.revision,
|
|
175
|
+
handlerArtifactDigest: input.handlerArtifactDigest,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function decisionAuthority(input, label, decision) {
|
|
180
|
+
exact(input, new Set(["descriptor", "verify"]), label);
|
|
181
|
+
const descriptor = authorityDescriptor(input.descriptor, label);
|
|
182
|
+
const verify = capture(input, "verify", label);
|
|
183
|
+
return Object.freeze({ descriptor, verify, decision });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function validateVerification(value, request, authority) {
|
|
187
|
+
exact(value, VERIFICATION_KEYS, `${authority.decision} verification`);
|
|
188
|
+
if (
|
|
189
|
+
value.authenticated !== true ||
|
|
190
|
+
value.durable !== true ||
|
|
191
|
+
value.authorityId !== authority.descriptor.authorityId ||
|
|
192
|
+
value.authorityRevision !== authority.descriptor.revision ||
|
|
193
|
+
value.handlerArtifactDigest !==
|
|
194
|
+
authority.descriptor.handlerArtifactDigest ||
|
|
195
|
+
value.requestDigest !== request.requestDigest ||
|
|
196
|
+
value.decision !== authority.decision ||
|
|
197
|
+
!DIGEST.test(value.receiptDigest ?? "") ||
|
|
198
|
+
!Number.isFinite(Date.parse(value.verifiedAt)) ||
|
|
199
|
+
new Date(Date.parse(value.verifiedAt)).toISOString() !== value.verifiedAt
|
|
200
|
+
) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
`quarantine release ${authority.decision} was not independently verified`,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
return freeze(clone(value));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function validateStoredVerification(value, request, decision) {
|
|
209
|
+
exact(value, VERIFICATION_KEYS, `stored ${decision} verification`);
|
|
210
|
+
if (
|
|
211
|
+
value.authenticated !== true ||
|
|
212
|
+
value.durable !== true ||
|
|
213
|
+
!ID.test(value.authorityId ?? "") ||
|
|
214
|
+
!Number.isSafeInteger(value.authorityRevision) ||
|
|
215
|
+
value.authorityRevision < 1 ||
|
|
216
|
+
!DIGEST.test(value.handlerArtifactDigest ?? "") ||
|
|
217
|
+
value.requestDigest !== request.requestDigest ||
|
|
218
|
+
!DIGEST.test(value.receiptDigest ?? "") ||
|
|
219
|
+
value.decision !== decision ||
|
|
220
|
+
!Number.isFinite(Date.parse(value.verifiedAt)) ||
|
|
221
|
+
new Date(Date.parse(value.verifiedAt)).toISOString() !== value.verifiedAt
|
|
222
|
+
) {
|
|
223
|
+
fail(`stored ${decision} verification is invalid`);
|
|
224
|
+
}
|
|
225
|
+
return value;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function createGovernedKnowledgeQuarantineReleaseDecisionAuthority({
|
|
229
|
+
approval,
|
|
230
|
+
evaluation,
|
|
231
|
+
} = {}) {
|
|
232
|
+
if (approval === evaluation) {
|
|
233
|
+
throw new TypeError(
|
|
234
|
+
"quarantine release approval and evaluation must be independent",
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
const approvalAuthority = decisionAuthority(
|
|
238
|
+
approval,
|
|
239
|
+
"approval authority",
|
|
240
|
+
"approved",
|
|
241
|
+
);
|
|
242
|
+
const evaluationAuthority = decisionAuthority(
|
|
243
|
+
evaluation,
|
|
244
|
+
"evaluation authority",
|
|
245
|
+
"passed",
|
|
246
|
+
);
|
|
247
|
+
if (
|
|
248
|
+
approvalAuthority.descriptor.authorityId ===
|
|
249
|
+
evaluationAuthority.descriptor.authorityId ||
|
|
250
|
+
approvalAuthority.descriptor.handlerArtifactDigest ===
|
|
251
|
+
evaluationAuthority.descriptor.handlerArtifactDigest
|
|
252
|
+
) {
|
|
253
|
+
throw new TypeError(
|
|
254
|
+
"quarantine release approval and evaluation identities must differ",
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
const authority = Object.freeze({
|
|
258
|
+
approval: approvalAuthority.descriptor,
|
|
259
|
+
evaluation: evaluationAuthority.descriptor,
|
|
260
|
+
});
|
|
261
|
+
DECISIONS.set(
|
|
262
|
+
authority,
|
|
263
|
+
Object.freeze(async ({ request, approvalReceipt, evaluationReceipt }) => {
|
|
264
|
+
const approvalResult = validateVerification(
|
|
265
|
+
await approvalAuthority.verify({
|
|
266
|
+
request: freeze(clone(request)),
|
|
267
|
+
receipt: freeze(clone(approvalReceipt)),
|
|
268
|
+
}),
|
|
269
|
+
request,
|
|
270
|
+
approvalAuthority,
|
|
271
|
+
);
|
|
272
|
+
const evaluationResult = validateVerification(
|
|
273
|
+
await evaluationAuthority.verify({
|
|
274
|
+
request: freeze(clone(request)),
|
|
275
|
+
receipt: freeze(clone(evaluationReceipt)),
|
|
276
|
+
}),
|
|
277
|
+
request,
|
|
278
|
+
evaluationAuthority,
|
|
279
|
+
);
|
|
280
|
+
if (approvalResult.receiptDigest === evaluationResult.receiptDigest) {
|
|
281
|
+
throw new Error(
|
|
282
|
+
"quarantine release approval and evaluation receipts must differ",
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
return freeze({
|
|
286
|
+
approval: approvalResult,
|
|
287
|
+
evaluation: evaluationResult,
|
|
288
|
+
});
|
|
289
|
+
}),
|
|
290
|
+
);
|
|
291
|
+
return authority;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function normalizeDependency(value) {
|
|
295
|
+
exact(value, DEPENDENCY_KEYS, "quarantine release dependency");
|
|
296
|
+
if (
|
|
297
|
+
!["active-skill", "candidate", "wiki"].includes(value.kind) ||
|
|
298
|
+
!DIGEST.test(value.digest ?? "") ||
|
|
299
|
+
value.disposition !== "quarantine"
|
|
300
|
+
) {
|
|
301
|
+
throw new TypeError("quarantine release dependency is invalid");
|
|
302
|
+
}
|
|
303
|
+
return { kind: value.kind, digest: value.digest, disposition: "quarantine" };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function normalizeDependencies(value) {
|
|
307
|
+
if (
|
|
308
|
+
!Array.isArray(value) ||
|
|
309
|
+
value.length < 1 ||
|
|
310
|
+
value.length > 256 ||
|
|
311
|
+
Object.keys(value).length !== value.length
|
|
312
|
+
) {
|
|
313
|
+
throw new TypeError("quarantine release dependencies are invalid");
|
|
314
|
+
}
|
|
315
|
+
const dependencies = value.map(normalizeDependency);
|
|
316
|
+
const identities = dependencies.map(
|
|
317
|
+
(item) => `${item.kind}\0${item.digest}\0${item.disposition}`,
|
|
318
|
+
);
|
|
319
|
+
if (new Set(identities).size !== identities.length) {
|
|
320
|
+
throw new TypeError("quarantine release dependencies must be unique");
|
|
321
|
+
}
|
|
322
|
+
return dependencies;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function validateRequest(value, tenantId) {
|
|
326
|
+
exact(value, REQUEST_KEYS, "quarantine release request");
|
|
327
|
+
const dependencies = normalizeDependencies(value.dependencies);
|
|
328
|
+
if (
|
|
329
|
+
value.schema !== GOVERNED_KNOWLEDGE_QUARANTINE_RELEASE_REQUEST_SCHEMA ||
|
|
330
|
+
value.tenantId !== tenantId ||
|
|
331
|
+
!DIGEST.test(value.operationDigest ?? "") ||
|
|
332
|
+
!ID.test(value.knowledgeId ?? "") ||
|
|
333
|
+
!DIGEST.test(value.contentDigest ?? "") ||
|
|
334
|
+
!DIGEST.test(value.preparedRecordDigest ?? "") ||
|
|
335
|
+
!DIGEST.test(value.settlementRecordDigest ?? "") ||
|
|
336
|
+
typeof value.reason !== "string" ||
|
|
337
|
+
value.reason.trim() !== value.reason ||
|
|
338
|
+
value.reason.length < 8 ||
|
|
339
|
+
value.reason.length > 2048
|
|
340
|
+
) {
|
|
341
|
+
throw new TypeError("quarantine release request binding is invalid");
|
|
342
|
+
}
|
|
343
|
+
const core = clone(value);
|
|
344
|
+
delete core.requestDigest;
|
|
345
|
+
if (
|
|
346
|
+
value.requestDigest !==
|
|
347
|
+
hash(GOVERNED_KNOWLEDGE_QUARANTINE_RELEASE_REQUEST_SCHEMA, core)
|
|
348
|
+
) {
|
|
349
|
+
throw new TypeError("quarantine release request digest is invalid");
|
|
350
|
+
}
|
|
351
|
+
return freeze({ ...clone(value), dependencies });
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function recordCore(value) {
|
|
355
|
+
const core = clone(value);
|
|
356
|
+
delete core.recordDigest;
|
|
357
|
+
return core;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export function digestGovernedKnowledgeQuarantineRelease(value) {
|
|
361
|
+
return hash(GOVERNED_KNOWLEDGE_QUARANTINE_RELEASE_SCHEMA, recordCore(value));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export function verifyGovernedKnowledgeQuarantineReleaseRecord(
|
|
365
|
+
value,
|
|
366
|
+
{ tenantId } = {},
|
|
367
|
+
) {
|
|
368
|
+
exact(value, RECORD_KEYS, "quarantine release record");
|
|
369
|
+
const request = validateRequest(
|
|
370
|
+
{
|
|
371
|
+
schema: GOVERNED_KNOWLEDGE_QUARANTINE_RELEASE_REQUEST_SCHEMA,
|
|
372
|
+
tenantId: value.tenantId,
|
|
373
|
+
operationDigest: value.operationDigest,
|
|
374
|
+
knowledgeId: value.knowledgeId,
|
|
375
|
+
contentDigest: value.contentDigest,
|
|
376
|
+
dependencies: value.dependencies,
|
|
377
|
+
preparedRecordDigest: value.preparedRecordDigest,
|
|
378
|
+
settlementRecordDigest: value.settlementRecordDigest,
|
|
379
|
+
reason: value.reason,
|
|
380
|
+
requestDigest: value.requestDigest,
|
|
381
|
+
},
|
|
382
|
+
tenantId,
|
|
383
|
+
);
|
|
384
|
+
validateStoredVerification(value.approval, request, "approved");
|
|
385
|
+
validateStoredVerification(value.evaluation, request, "passed");
|
|
386
|
+
if (
|
|
387
|
+
value.schema !== GOVERNED_KNOWLEDGE_QUARANTINE_RELEASE_SCHEMA ||
|
|
388
|
+
value.tenantId !== tenantId ||
|
|
389
|
+
value.approval.authenticated !== true ||
|
|
390
|
+
value.approval.durable !== true ||
|
|
391
|
+
value.approval.decision !== "approved" ||
|
|
392
|
+
value.approval.requestDigest !== request.requestDigest ||
|
|
393
|
+
value.evaluation.authenticated !== true ||
|
|
394
|
+
value.evaluation.durable !== true ||
|
|
395
|
+
value.evaluation.decision !== "passed" ||
|
|
396
|
+
value.evaluation.requestDigest !== request.requestDigest ||
|
|
397
|
+
value.approval.authorityId === value.evaluation.authorityId ||
|
|
398
|
+
value.approval.handlerArtifactDigest ===
|
|
399
|
+
value.evaluation.handlerArtifactDigest ||
|
|
400
|
+
value.approval.receiptDigest === value.evaluation.receiptDigest ||
|
|
401
|
+
!Number.isFinite(Date.parse(value.committedAt)) ||
|
|
402
|
+
new Date(Date.parse(value.committedAt)).toISOString() !==
|
|
403
|
+
value.committedAt ||
|
|
404
|
+
value.recordDigest !== digestGovernedKnowledgeQuarantineRelease(value)
|
|
405
|
+
) {
|
|
406
|
+
fail("quarantine release record binding is invalid");
|
|
407
|
+
}
|
|
408
|
+
return freeze(clone(value));
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function normalizeDescriptor(input) {
|
|
412
|
+
return freeze({
|
|
413
|
+
tenantId: identifier(input?.tenantId, "tenantId"),
|
|
414
|
+
artifactTenantId: identifier(input?.artifactTenantId, "artifactTenantId"),
|
|
415
|
+
streamId: identifier(input?.streamId, "streamId"),
|
|
416
|
+
audience: identifier(input?.audience, "audience"),
|
|
417
|
+
purpose: identifier(input?.purpose, "purpose"),
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function parseArtifact(resolution, descriptor) {
|
|
422
|
+
if (
|
|
423
|
+
resolution?.schema !== EVOLUTION_ARTIFACT_RESOLUTION_SCHEMA ||
|
|
424
|
+
resolution.authenticated !== true ||
|
|
425
|
+
resolution.found !== true ||
|
|
426
|
+
!DIGEST.test(resolution.digest ?? "") ||
|
|
427
|
+
!DIGEST.test(resolution.receiptDigest ?? "") ||
|
|
428
|
+
!Buffer.isBuffer(resolution.bytes)
|
|
429
|
+
) {
|
|
430
|
+
fail("quarantine release artifact resolution is incomplete");
|
|
431
|
+
}
|
|
432
|
+
let artifact;
|
|
433
|
+
try {
|
|
434
|
+
artifact = JSON.parse(resolution.bytes.toString("utf8"));
|
|
435
|
+
} catch {
|
|
436
|
+
fail("quarantine release artifact is not JSON");
|
|
437
|
+
}
|
|
438
|
+
if (
|
|
439
|
+
artifact?.schema !== EVOLUTION_DURABLE_ARTIFACT_RECORD_SCHEMA ||
|
|
440
|
+
artifact.tenantId !== descriptor.artifactTenantId ||
|
|
441
|
+
artifact.audience !== descriptor.audience ||
|
|
442
|
+
artifact.purpose !== descriptor.purpose ||
|
|
443
|
+
artifact.retention !== "ledger" ||
|
|
444
|
+
artifact.type !== ARTIFACT_TYPE
|
|
445
|
+
) {
|
|
446
|
+
fail("quarantine release durable artifact binding is invalid");
|
|
447
|
+
}
|
|
448
|
+
return verifyGovernedKnowledgeQuarantineReleaseRecord(artifact.value, {
|
|
449
|
+
tenantId: descriptor.tenantId,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function appendWikiReleaseLog(state, patternId, release) {
|
|
454
|
+
const entry = {
|
|
455
|
+
sequence: state.evolutionLog.length + 1,
|
|
456
|
+
type: "pattern-quarantine-released",
|
|
457
|
+
subjectId: patternId,
|
|
458
|
+
effectiveAt: release.committedAt,
|
|
459
|
+
details: {
|
|
460
|
+
operationDigest: release.operationDigest,
|
|
461
|
+
releaseRecordDigest: release.recordDigest,
|
|
462
|
+
},
|
|
463
|
+
};
|
|
464
|
+
entry.entryDigest = wikiHash(entry);
|
|
465
|
+
state.evolutionLog.push(entry);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function hasWikiReleaseLog(state, patternId, release) {
|
|
469
|
+
return state.evolutionLog.some(
|
|
470
|
+
(entry) =>
|
|
471
|
+
entry.type === "pattern-quarantine-released" &&
|
|
472
|
+
entry.subjectId === patternId &&
|
|
473
|
+
entry.details?.operationDigest === release.operationDigest &&
|
|
474
|
+
entry.details?.releaseRecordDigest === release.recordDigest,
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function wikiReleaseRevision({
|
|
479
|
+
current,
|
|
480
|
+
original,
|
|
481
|
+
targetIds,
|
|
482
|
+
release,
|
|
483
|
+
reader,
|
|
484
|
+
}) {
|
|
485
|
+
const state = clone(current.state);
|
|
486
|
+
for (const patternId of targetIds) {
|
|
487
|
+
const before = original.state.patterns[patternId];
|
|
488
|
+
const quarantined = state.patterns[patternId];
|
|
489
|
+
if (!before || !quarantined) {
|
|
490
|
+
throw new Error("quarantine release Wiki target is missing");
|
|
491
|
+
}
|
|
492
|
+
if (quarantined.status !== "quarantined") {
|
|
493
|
+
throw new Error(
|
|
494
|
+
"quarantine release cannot overwrite a non-quarantined Wiki target",
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
state.patterns[patternId] = clone(before);
|
|
498
|
+
state.patterns[patternId].updatedAt = release.committedAt;
|
|
499
|
+
appendWikiReleaseLog(state, patternId, release);
|
|
500
|
+
}
|
|
501
|
+
state.index = Object.values(state.patterns)
|
|
502
|
+
.filter(
|
|
503
|
+
(pattern) =>
|
|
504
|
+
!["revoked", "tombstoned", "quarantined", "stale"].includes(
|
|
505
|
+
pattern.status,
|
|
506
|
+
),
|
|
507
|
+
)
|
|
508
|
+
.map((pattern) => ({
|
|
509
|
+
patternId: pattern.patternId,
|
|
510
|
+
kind: pattern.kind,
|
|
511
|
+
status: pattern.status,
|
|
512
|
+
summary: pattern.summary,
|
|
513
|
+
confidence: pattern.operationalConfidence,
|
|
514
|
+
actionable: pattern.actionable,
|
|
515
|
+
skillNames: pattern.skillNames,
|
|
516
|
+
}))
|
|
517
|
+
.sort((left, right) => left.patternId.localeCompare(right.patternId));
|
|
518
|
+
state.revision += 1;
|
|
519
|
+
const evidenceRefs = Object.keys(state.evidence ?? {})
|
|
520
|
+
.sort()
|
|
521
|
+
.slice(0, 1);
|
|
522
|
+
if (evidenceRefs.length !== 1) {
|
|
523
|
+
throw new Error(
|
|
524
|
+
"quarantine release requires retained authenticated Wiki evidence",
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
const operations = targetIds.map((patternId) => ({
|
|
528
|
+
type: "release-quarantine",
|
|
529
|
+
patternId,
|
|
530
|
+
releaseRecordDigest: release.recordDigest,
|
|
531
|
+
}));
|
|
532
|
+
const payload = {
|
|
533
|
+
schema: WIKI_REVISION_SCHEMA,
|
|
534
|
+
tenantId: reader.descriptor.tenantId,
|
|
535
|
+
evolutionRunId: reader.descriptor.evolutionRunId,
|
|
536
|
+
revision: state.revision,
|
|
537
|
+
priorStateDigest: current.stateDigest,
|
|
538
|
+
rulesDigest: original.rulesDigest,
|
|
539
|
+
maintainerModel: original.maintainerModel,
|
|
540
|
+
effectiveAt: release.committedAt,
|
|
541
|
+
evidenceRefs,
|
|
542
|
+
operationDigest: wikiHash(operations),
|
|
543
|
+
};
|
|
544
|
+
state.revisionId = `wiki:${wikiHash(payload).slice(7)}`;
|
|
545
|
+
return freeze({
|
|
546
|
+
...payload,
|
|
547
|
+
revisionId: state.revisionId,
|
|
548
|
+
stateDigest: digestWikiState(state),
|
|
549
|
+
state: freeze(state),
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
export class GovernedKnowledgeQuarantineReleaseLedger {
|
|
554
|
+
constructor({
|
|
555
|
+
descriptor: input,
|
|
556
|
+
artifactPorts,
|
|
557
|
+
ledger,
|
|
558
|
+
ledgerArtifactResolver,
|
|
559
|
+
dependencyExecutor,
|
|
560
|
+
decisionAuthority,
|
|
561
|
+
now = Date.now,
|
|
562
|
+
} = {}) {
|
|
563
|
+
this.descriptor = normalizeDescriptor(input);
|
|
564
|
+
this._put = capture(artifactPorts, "putCanonical", "artifactPorts");
|
|
565
|
+
this._readLedger = capture(ledger, "read", "ledger");
|
|
566
|
+
this._verifyLedger = capture(ledger, "verify", "ledger");
|
|
567
|
+
this._append = capture(ledger, "appendDomainEvent", "ledger");
|
|
568
|
+
if (!isEvolutionLedgerArtifactResolver(ledgerArtifactResolver)) {
|
|
569
|
+
throw new TypeError(
|
|
570
|
+
"a branded EvolutionArtifactPorts ledger resolver is required",
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
if (typeof now !== "function" || utilTypes.isProxy(now)) {
|
|
574
|
+
throw new TypeError("now must be a non-proxy function");
|
|
575
|
+
}
|
|
576
|
+
const verifyDecision = DECISIONS.get(decisionAuthority);
|
|
577
|
+
if (!verifyDecision) {
|
|
578
|
+
throw new TypeError(
|
|
579
|
+
"a governed quarantine release decision authority is required",
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
this._resolve = ledgerArtifactResolver;
|
|
583
|
+
this._ledger = ledger;
|
|
584
|
+
this._readPrepared = capture(
|
|
585
|
+
dependencyExecutor,
|
|
586
|
+
"readPrepared",
|
|
587
|
+
"dependencyExecutor",
|
|
588
|
+
);
|
|
589
|
+
this._readSettlement = capture(
|
|
590
|
+
dependencyExecutor,
|
|
591
|
+
"readSettlement",
|
|
592
|
+
"dependencyExecutor",
|
|
593
|
+
);
|
|
594
|
+
this._verifyDecision = verifyDecision;
|
|
595
|
+
this._now = now;
|
|
596
|
+
Object.freeze(this);
|
|
597
|
+
RELEASES.add(this);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
_events() {
|
|
601
|
+
const events = this._readLedger();
|
|
602
|
+
if (!Array.isArray(events)) fail("EvolutionLedger returned no events");
|
|
603
|
+
return events.filter(
|
|
604
|
+
(event) =>
|
|
605
|
+
event.schema === EVOLUTION_LEDGER_DOMAIN_EVENT_SCHEMA &&
|
|
606
|
+
event.tenantId === this.descriptor.tenantId &&
|
|
607
|
+
event.type === GOVERNED_KNOWLEDGE_QUARANTINE_RELEASE_EVENT_TYPE,
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
async _entry(operationDigest) {
|
|
612
|
+
const eventId = `${GOVERNED_KNOWLEDGE_QUARANTINE_RELEASE_EVENT_TYPE}.${operationDigest.slice(7)}`;
|
|
613
|
+
const matches = this._events().filter((event) => event.eventId === eventId);
|
|
614
|
+
if (matches.length > 1) fail("quarantine release event is ambiguous");
|
|
615
|
+
if (matches.length === 0) return null;
|
|
616
|
+
const event = matches[0];
|
|
617
|
+
const identity = this._verifyLedger();
|
|
618
|
+
const resolution = await this._resolve({
|
|
619
|
+
epoch: identity.epoch,
|
|
620
|
+
ledgerId: identity.ledgerId,
|
|
621
|
+
ref: event.subjectRef,
|
|
622
|
+
tenantId: this.descriptor.artifactTenantId,
|
|
623
|
+
});
|
|
624
|
+
if (
|
|
625
|
+
resolution?.ref !== event.subjectRef.ref ||
|
|
626
|
+
resolution?.digest !== event.subjectRef.digest
|
|
627
|
+
) {
|
|
628
|
+
fail("quarantine release resolved a substituted artifact");
|
|
629
|
+
}
|
|
630
|
+
const record = parseArtifact(resolution, this.descriptor);
|
|
631
|
+
if (
|
|
632
|
+
record.operationDigest !== operationDigest ||
|
|
633
|
+
event.correlationId !== this.descriptor.streamId ||
|
|
634
|
+
event.decision !== "committed" ||
|
|
635
|
+
event.timestamp !== record.committedAt ||
|
|
636
|
+
!Array.isArray(event.sourceRefs) ||
|
|
637
|
+
event.sourceRefs.length !== 0
|
|
638
|
+
) {
|
|
639
|
+
fail("quarantine release event binding is invalid");
|
|
640
|
+
}
|
|
641
|
+
return { event, record };
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
async read({ operationDigest } = {}) {
|
|
645
|
+
if (!RELEASES.has(this) || !DIGEST.test(operationDigest ?? "")) {
|
|
646
|
+
throw new TypeError("quarantine release operation identity is invalid");
|
|
647
|
+
}
|
|
648
|
+
const entry = await this._entry(operationDigest);
|
|
649
|
+
return entry === null
|
|
650
|
+
? null
|
|
651
|
+
: freeze({
|
|
652
|
+
authenticated: true,
|
|
653
|
+
durable: true,
|
|
654
|
+
record: clone(entry.record),
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
async _wikiRevision(reader, revisionId) {
|
|
659
|
+
const eventId = `wiki.revision.${revisionId.slice("wiki:".length)}`;
|
|
660
|
+
const matches = this._readLedger().filter(
|
|
661
|
+
(event) =>
|
|
662
|
+
event.type === "wiki.revision.committed" &&
|
|
663
|
+
event.eventId === eventId &&
|
|
664
|
+
event.tenantId === this.descriptor.tenantId &&
|
|
665
|
+
event.correlationId === reader.descriptor.evolutionRunId,
|
|
666
|
+
);
|
|
667
|
+
if (matches.length !== 1) {
|
|
668
|
+
throw new Error(
|
|
669
|
+
"quarantine release Wiki revision is missing or ambiguous",
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
const event = matches[0];
|
|
673
|
+
const identity = this._verifyLedger();
|
|
674
|
+
const resolution = await this._resolve({
|
|
675
|
+
epoch: identity.epoch,
|
|
676
|
+
ledgerId: identity.ledgerId,
|
|
677
|
+
ref: event.subjectRef,
|
|
678
|
+
tenantId: this.descriptor.artifactTenantId,
|
|
679
|
+
});
|
|
680
|
+
if (
|
|
681
|
+
resolution?.schema !== EVOLUTION_ARTIFACT_RESOLUTION_SCHEMA ||
|
|
682
|
+
resolution.authenticated !== true ||
|
|
683
|
+
resolution.found !== true ||
|
|
684
|
+
resolution.ref !== event.subjectRef.ref ||
|
|
685
|
+
resolution.digest !== event.subjectRef.digest ||
|
|
686
|
+
!Buffer.isBuffer(resolution.bytes)
|
|
687
|
+
) {
|
|
688
|
+
fail("quarantine release Wiki revision resolution is invalid");
|
|
689
|
+
}
|
|
690
|
+
let artifact;
|
|
691
|
+
try {
|
|
692
|
+
artifact = JSON.parse(resolution.bytes.toString("utf8"));
|
|
693
|
+
} catch {
|
|
694
|
+
fail("quarantine release Wiki revision artifact is not JSON");
|
|
695
|
+
}
|
|
696
|
+
if (
|
|
697
|
+
artifact?.schema !== EVOLUTION_DURABLE_ARTIFACT_RECORD_SCHEMA ||
|
|
698
|
+
artifact.tenantId !== this.descriptor.artifactTenantId ||
|
|
699
|
+
artifact.audience !== this.descriptor.audience ||
|
|
700
|
+
artifact.purpose !== this.descriptor.purpose ||
|
|
701
|
+
artifact.retention !== "ledger" ||
|
|
702
|
+
artifact.type !== "wiki-revision"
|
|
703
|
+
) {
|
|
704
|
+
fail("quarantine release Wiki revision artifact binding is invalid");
|
|
705
|
+
}
|
|
706
|
+
return verifyWikiRevision(artifact.value, reader.descriptor);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
async restoreWikis({ operationDigest, wikiAdapters } = {}) {
|
|
710
|
+
if (!RELEASES.has(this) || !DIGEST.test(operationDigest ?? "")) {
|
|
711
|
+
throw new TypeError("quarantine release operation identity is invalid");
|
|
712
|
+
}
|
|
713
|
+
if (
|
|
714
|
+
!Array.isArray(wikiAdapters) ||
|
|
715
|
+
wikiAdapters.length < 1 ||
|
|
716
|
+
wikiAdapters.length > 64 ||
|
|
717
|
+
Object.keys(wikiAdapters).length !== wikiAdapters.length ||
|
|
718
|
+
new Set(wikiAdapters).size !== wikiAdapters.length
|
|
719
|
+
) {
|
|
720
|
+
throw new TypeError("quarantine release Wiki adapters are invalid");
|
|
721
|
+
}
|
|
722
|
+
const entry = await this._entry(operationDigest);
|
|
723
|
+
if (!entry) {
|
|
724
|
+
throw new Error("quarantine release was not durably committed");
|
|
725
|
+
}
|
|
726
|
+
const release = entry.record;
|
|
727
|
+
const dependencies = release.dependencies.filter(
|
|
728
|
+
(dependency) => dependency.kind === "wiki",
|
|
729
|
+
);
|
|
730
|
+
if (dependencies.length === 0) {
|
|
731
|
+
return freeze({
|
|
732
|
+
authenticated: true,
|
|
733
|
+
durable: true,
|
|
734
|
+
recovered: true,
|
|
735
|
+
operationDigest,
|
|
736
|
+
results: [],
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
const bindings = wikiAdapters.map((adapter) => {
|
|
740
|
+
const reader = captureWikiRevisionReader(adapter);
|
|
741
|
+
if (
|
|
742
|
+
reader.descriptor.tenantId !== this.descriptor.tenantId ||
|
|
743
|
+
!reader.matchesLedger(this._ledger)
|
|
744
|
+
) {
|
|
745
|
+
throw new TypeError(
|
|
746
|
+
"quarantine release Wiki adapter must share the genuine tenant Ledger",
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
return {
|
|
750
|
+
adapter,
|
|
751
|
+
reader,
|
|
752
|
+
commit: capture(adapter, "commitRevision", "wikiAdapter"),
|
|
753
|
+
};
|
|
754
|
+
});
|
|
755
|
+
const results = [];
|
|
756
|
+
for (const dependency of dependencies) {
|
|
757
|
+
const matches = bindings
|
|
758
|
+
.map((binding) => ({
|
|
759
|
+
binding,
|
|
760
|
+
original: binding.reader.findStateRevision({
|
|
761
|
+
tenantId: this.descriptor.tenantId,
|
|
762
|
+
stateDigest: dependency.digest,
|
|
763
|
+
}),
|
|
764
|
+
}))
|
|
765
|
+
.filter((entryValue) => entryValue.original !== null);
|
|
766
|
+
if (matches.length !== 1) {
|
|
767
|
+
throw new Error(
|
|
768
|
+
"quarantine release Wiki source is missing or ambiguous",
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
const { binding, original } = matches[0];
|
|
772
|
+
const provenance = binding.reader.readKnowledgeProvenance({
|
|
773
|
+
tenantId: this.descriptor.tenantId,
|
|
774
|
+
revisionId: original.revisionId,
|
|
775
|
+
knowledgeId: release.knowledgeId,
|
|
776
|
+
contentDigest: release.contentDigest,
|
|
777
|
+
});
|
|
778
|
+
if (
|
|
779
|
+
provenance.authenticated !== true ||
|
|
780
|
+
provenance.stateDigest !== dependency.digest ||
|
|
781
|
+
!Array.isArray(provenance.affectedPatternIds) ||
|
|
782
|
+
provenance.affectedPatternIds.length < 1
|
|
783
|
+
) {
|
|
784
|
+
throw new Error(
|
|
785
|
+
"quarantine release Wiki provenance is not authenticated and complete",
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
const targetIds = provenance.affectedPatternIds;
|
|
789
|
+
const originalRevision = await this._wikiRevision(
|
|
790
|
+
binding.reader,
|
|
791
|
+
original.revisionId,
|
|
792
|
+
);
|
|
793
|
+
let current = binding.reader.loadWiki();
|
|
794
|
+
const alreadyRestored = targetIds.every(
|
|
795
|
+
(patternId) =>
|
|
796
|
+
current.state.patterns[patternId]?.status !== "quarantined" &&
|
|
797
|
+
hasWikiReleaseLog(current.state, patternId, release),
|
|
798
|
+
);
|
|
799
|
+
if (!alreadyRestored) {
|
|
800
|
+
const currentRevision = binding.reader.readStateRevision({
|
|
801
|
+
tenantId: this.descriptor.tenantId,
|
|
802
|
+
stateDigest: current.stateDigest,
|
|
803
|
+
});
|
|
804
|
+
const revision = wikiReleaseRevision({
|
|
805
|
+
current,
|
|
806
|
+
original: {
|
|
807
|
+
...original,
|
|
808
|
+
rulesDigest: originalRevision.rulesDigest,
|
|
809
|
+
maintainerModel: originalRevision.maintainerModel,
|
|
810
|
+
},
|
|
811
|
+
targetIds,
|
|
812
|
+
release,
|
|
813
|
+
reader: binding.reader,
|
|
814
|
+
});
|
|
815
|
+
let commitError = null;
|
|
816
|
+
try {
|
|
817
|
+
const expectedLedgerHead = Object.fromEntries(
|
|
818
|
+
[
|
|
819
|
+
"epoch",
|
|
820
|
+
"ledgerId",
|
|
821
|
+
"identityDigest",
|
|
822
|
+
"sequence",
|
|
823
|
+
"headDigest",
|
|
824
|
+
].map((key) => [key, currentRevision.ledgerHead[key]]),
|
|
825
|
+
);
|
|
826
|
+
binding.commit({
|
|
827
|
+
expectedStateDigest: current.stateDigest,
|
|
828
|
+
expectedLedgerHead,
|
|
829
|
+
revision,
|
|
830
|
+
});
|
|
831
|
+
} catch (error) {
|
|
832
|
+
commitError = error;
|
|
833
|
+
}
|
|
834
|
+
current = binding.reader.loadWiki();
|
|
835
|
+
const completed = targetIds.every(
|
|
836
|
+
(patternId) =>
|
|
837
|
+
current.state.patterns[patternId]?.status !== "quarantined" &&
|
|
838
|
+
hasWikiReleaseLog(current.state, patternId, release),
|
|
839
|
+
);
|
|
840
|
+
if (!completed) {
|
|
841
|
+
if (commitError) throw commitError;
|
|
842
|
+
throw new Error("quarantine release Wiki commit was not recovered");
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
results.push({
|
|
846
|
+
dependencyDigest: dependency.digest,
|
|
847
|
+
evolutionRunId: binding.reader.descriptor.evolutionRunId,
|
|
848
|
+
stateDigest: current.stateDigest,
|
|
849
|
+
revisionId: current.state.revisionId,
|
|
850
|
+
recovered: alreadyRestored,
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
return freeze({
|
|
854
|
+
authenticated: true,
|
|
855
|
+
durable: true,
|
|
856
|
+
recovered: results.every((result) => result.recovered),
|
|
857
|
+
operationDigest,
|
|
858
|
+
results,
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
async commit({
|
|
863
|
+
operationDigest,
|
|
864
|
+
reason,
|
|
865
|
+
approvalReceipt,
|
|
866
|
+
evaluationReceipt,
|
|
867
|
+
} = {}) {
|
|
868
|
+
if (!RELEASES.has(this) || !DIGEST.test(operationDigest ?? "")) {
|
|
869
|
+
throw new TypeError("quarantine release operation identity is invalid");
|
|
870
|
+
}
|
|
871
|
+
const prepared = this._readPrepared({ operationDigest });
|
|
872
|
+
const settlement = this._readSettlement({ operationDigest });
|
|
873
|
+
if (
|
|
874
|
+
prepared?.authenticated !== true ||
|
|
875
|
+
prepared.durable !== true ||
|
|
876
|
+
settlement?.authenticated !== true ||
|
|
877
|
+
settlement.durable !== true
|
|
878
|
+
) {
|
|
879
|
+
throw new Error(
|
|
880
|
+
"quarantine release requires settled authenticated dependency effects",
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
const dependencies = normalizeDependencies(
|
|
884
|
+
prepared.knowledge.dependencies.filter(
|
|
885
|
+
(dependency) => dependency.disposition === "quarantine",
|
|
886
|
+
),
|
|
887
|
+
);
|
|
888
|
+
const requestCore = {
|
|
889
|
+
schema: GOVERNED_KNOWLEDGE_QUARANTINE_RELEASE_REQUEST_SCHEMA,
|
|
890
|
+
tenantId: this.descriptor.tenantId,
|
|
891
|
+
operationDigest,
|
|
892
|
+
knowledgeId: prepared.knowledge.knowledgeId,
|
|
893
|
+
contentDigest: prepared.knowledge.contentDigest,
|
|
894
|
+
dependencies,
|
|
895
|
+
preparedRecordDigest: prepared.preparedRecordDigest,
|
|
896
|
+
settlementRecordDigest: settlement.record.recordDigest,
|
|
897
|
+
reason,
|
|
898
|
+
};
|
|
899
|
+
const request = validateRequest(
|
|
900
|
+
{
|
|
901
|
+
...requestCore,
|
|
902
|
+
requestDigest: hash(
|
|
903
|
+
GOVERNED_KNOWLEDGE_QUARANTINE_RELEASE_REQUEST_SCHEMA,
|
|
904
|
+
requestCore,
|
|
905
|
+
),
|
|
906
|
+
},
|
|
907
|
+
this.descriptor.tenantId,
|
|
908
|
+
);
|
|
909
|
+
const decisions = await this._verifyDecision({
|
|
910
|
+
request,
|
|
911
|
+
approvalReceipt,
|
|
912
|
+
evaluationReceipt,
|
|
913
|
+
});
|
|
914
|
+
const existing = await this._entry(operationDigest);
|
|
915
|
+
if (existing) {
|
|
916
|
+
if (
|
|
917
|
+
existing.record.requestDigest !== request.requestDigest ||
|
|
918
|
+
canonical(existing.record.approval) !== canonical(decisions.approval) ||
|
|
919
|
+
canonical(existing.record.evaluation) !==
|
|
920
|
+
canonical(decisions.evaluation)
|
|
921
|
+
) {
|
|
922
|
+
fail("quarantine release operation resolved different approval work");
|
|
923
|
+
}
|
|
924
|
+
return freeze({
|
|
925
|
+
authenticated: true,
|
|
926
|
+
durable: true,
|
|
927
|
+
recovered: true,
|
|
928
|
+
record: clone(existing.record),
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
const milliseconds = Number(this._now());
|
|
932
|
+
if (!Number.isFinite(milliseconds)) {
|
|
933
|
+
throw new TypeError("quarantine release clock is invalid");
|
|
934
|
+
}
|
|
935
|
+
const core = {
|
|
936
|
+
schema: GOVERNED_KNOWLEDGE_QUARANTINE_RELEASE_SCHEMA,
|
|
937
|
+
...Object.fromEntries(
|
|
938
|
+
Object.entries(request).filter(([key]) => key !== "schema"),
|
|
939
|
+
),
|
|
940
|
+
approval: decisions.approval,
|
|
941
|
+
evaluation: decisions.evaluation,
|
|
942
|
+
committedAt: new Date(milliseconds).toISOString(),
|
|
943
|
+
};
|
|
944
|
+
const record = verifyGovernedKnowledgeQuarantineReleaseRecord(
|
|
945
|
+
{
|
|
946
|
+
...core,
|
|
947
|
+
recordDigest: digestGovernedKnowledgeQuarantineRelease(core),
|
|
948
|
+
},
|
|
949
|
+
{ tenantId: this.descriptor.tenantId },
|
|
950
|
+
);
|
|
951
|
+
const head = this._verifyLedger();
|
|
952
|
+
const published = this._put(ARTIFACT_TYPE, record, {
|
|
953
|
+
audience: this.descriptor.audience,
|
|
954
|
+
purpose: this.descriptor.purpose,
|
|
955
|
+
retention: "ledger",
|
|
956
|
+
});
|
|
957
|
+
if (
|
|
958
|
+
!published?.ref ||
|
|
959
|
+
published.receipt?.persisted !== true ||
|
|
960
|
+
published.receipt?.readbackVerified !== true ||
|
|
961
|
+
published.receipt?.integrityVerified !== true ||
|
|
962
|
+
published.receipt?.retention !== "ledger"
|
|
963
|
+
) {
|
|
964
|
+
fail("quarantine release artifact was not durably read back");
|
|
965
|
+
}
|
|
966
|
+
const eventId = `${GOVERNED_KNOWLEDGE_QUARANTINE_RELEASE_EVENT_TYPE}.${operationDigest.slice(7)}`;
|
|
967
|
+
let recoveredAfterAppend = false;
|
|
968
|
+
try {
|
|
969
|
+
const receipt = await this._append(
|
|
970
|
+
{
|
|
971
|
+
artifactTenantId: this.descriptor.artifactTenantId,
|
|
972
|
+
correlationId: this.descriptor.streamId,
|
|
973
|
+
decision: "committed",
|
|
974
|
+
eventId,
|
|
975
|
+
reason: "governed knowledge quarantine released",
|
|
976
|
+
skillName: null,
|
|
977
|
+
sourceRefs: [],
|
|
978
|
+
subjectRef: published.ref,
|
|
979
|
+
tenantId: this.descriptor.tenantId,
|
|
980
|
+
timestamp: record.committedAt,
|
|
981
|
+
type: GOVERNED_KNOWLEDGE_QUARANTINE_RELEASE_EVENT_TYPE,
|
|
982
|
+
},
|
|
983
|
+
{
|
|
984
|
+
expectedHeadDigest: head.headDigest,
|
|
985
|
+
expectedSequence: head.sequence,
|
|
986
|
+
},
|
|
987
|
+
);
|
|
988
|
+
if (receipt?.authenticated !== true || receipt.durable !== true) {
|
|
989
|
+
fail("quarantine release append was not durably confirmed");
|
|
990
|
+
}
|
|
991
|
+
} catch (error) {
|
|
992
|
+
const recovered = await this._entry(operationDigest);
|
|
993
|
+
if (!recovered || canonical(recovered.record) !== canonical(record)) {
|
|
994
|
+
throw error;
|
|
995
|
+
}
|
|
996
|
+
recoveredAfterAppend = true;
|
|
997
|
+
}
|
|
998
|
+
const stored = await this._entry(operationDigest);
|
|
999
|
+
if (!stored || canonical(stored.record) !== canonical(record)) {
|
|
1000
|
+
fail("quarantine release readback differs after commit");
|
|
1001
|
+
}
|
|
1002
|
+
return freeze({
|
|
1003
|
+
authenticated: true,
|
|
1004
|
+
durable: true,
|
|
1005
|
+
recovered: recoveredAfterAppend,
|
|
1006
|
+
record: clone(record),
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
export function createGovernedKnowledgeQuarantineReleaseLedger(options) {
|
|
1012
|
+
return new GovernedKnowledgeQuarantineReleaseLedger(options);
|
|
1013
|
+
}
|