@superbee/core 0.2.0-pre.4 → 0.2.0-pre.41
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 +1255 -0
- package/dist/artifact-contract.d.ts +90 -0
- package/dist/artifact-contract.d.ts.map +1 -0
- package/dist/artifact-contract.js +205 -0
- package/dist/artifact-contract.js.map +1 -0
- package/dist/backend.d.ts +22 -2
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.js +153 -38
- package/dist/backend.js.map +1 -1
- package/dist/blob-contract.d.ts +82 -0
- package/dist/blob-contract.d.ts.map +1 -0
- package/dist/blob-contract.js +134 -0
- package/dist/blob-contract.js.map +1 -0
- package/dist/blob-journal.d.ts +303 -0
- package/dist/blob-journal.d.ts.map +1 -0
- package/dist/blob-journal.js +541 -0
- package/dist/blob-journal.js.map +1 -0
- package/dist/body-replace-guards.d.ts +52 -0
- package/dist/body-replace-guards.d.ts.map +1 -0
- package/dist/body-replace-guards.js +176 -0
- package/dist/body-replace-guards.js.map +1 -0
- package/dist/builtin-recipes/content.d.ts +93 -0
- package/dist/builtin-recipes/content.d.ts.map +1 -0
- package/dist/builtin-recipes/content.js +288 -0
- package/dist/builtin-recipes/content.js.map +1 -0
- package/dist/builtin-recipes.d.ts +10 -0
- package/dist/builtin-recipes.d.ts.map +1 -0
- package/dist/builtin-recipes.js +97 -0
- package/dist/builtin-recipes.js.map +1 -0
- package/dist/bundle-health/index.d.ts +14 -0
- package/dist/bundle-health/index.d.ts.map +1 -0
- package/dist/bundle-health/index.js +12 -0
- package/dist/bundle-health/index.js.map +1 -0
- package/dist/bundle-health/kind-findings.d.ts +44 -0
- package/dist/bundle-health/kind-findings.d.ts.map +1 -0
- package/dist/bundle-health/kind-findings.js +68 -0
- package/dist/bundle-health/kind-findings.js.map +1 -0
- package/dist/bundle-health/legacy-page.d.ts +39 -0
- package/dist/bundle-health/legacy-page.d.ts.map +1 -0
- package/dist/bundle-health/legacy-page.js +50 -0
- package/dist/bundle-health/legacy-page.js.map +1 -0
- package/dist/bundle-health/report.d.ts +207 -0
- package/dist/bundle-health/report.d.ts.map +1 -0
- package/dist/bundle-health/report.js +454 -0
- package/dist/bundle-health/report.js.map +1 -0
- package/dist/bundle-ops.d.ts +3 -2
- package/dist/bundle-ops.d.ts.map +1 -1
- package/dist/bundle-ops.js +4 -4
- package/dist/bundle-ops.js.map +1 -1
- package/dist/bundle.d.ts +3 -4
- package/dist/bundle.d.ts.map +1 -1
- package/dist/bundle.js +5 -14
- package/dist/bundle.js.map +1 -1
- package/dist/code-point-order.d.ts +8 -0
- package/dist/code-point-order.d.ts.map +1 -0
- package/dist/code-point-order.js +20 -0
- package/dist/code-point-order.js.map +1 -0
- package/dist/code-unit-order.d.ts +15 -0
- package/dist/code-unit-order.d.ts.map +1 -0
- package/dist/code-unit-order.js +17 -0
- package/dist/code-unit-order.js.map +1 -0
- package/dist/connector-request.d.ts +112 -0
- package/dist/connector-request.d.ts.map +1 -0
- package/dist/connector-request.js +495 -0
- package/dist/connector-request.js.map +1 -0
- package/dist/connectors.d.ts +297 -0
- package/dist/connectors.d.ts.map +1 -0
- package/dist/connectors.js +1233 -0
- package/dist/connectors.js.map +1 -0
- package/dist/cross-bundle.d.ts +135 -0
- package/dist/cross-bundle.d.ts.map +1 -0
- package/dist/cross-bundle.js +191 -0
- package/dist/cross-bundle.js.map +1 -0
- package/dist/dependencies.d.ts +94 -0
- package/dist/dependencies.d.ts.map +1 -0
- package/dist/dependencies.js +180 -0
- package/dist/dependencies.js.map +1 -0
- package/dist/document-codec.d.ts +7 -2
- package/dist/document-codec.d.ts.map +1 -1
- package/dist/document-codec.js +8 -5
- package/dist/document-codec.js.map +1 -1
- package/dist/document-field-actions.d.ts +31 -1
- package/dist/document-field-actions.d.ts.map +1 -1
- package/dist/document-field-actions.js +137 -34
- package/dist/document-field-actions.js.map +1 -1
- package/dist/document-mutation.d.ts +37 -13
- package/dist/document-mutation.d.ts.map +1 -1
- package/dist/document-mutation.js +129 -60
- package/dist/document-mutation.js.map +1 -1
- package/dist/document-transfer.d.ts +208 -0
- package/dist/document-transfer.d.ts.map +1 -0
- package/dist/document-transfer.js +928 -0
- package/dist/document-transfer.js.map +1 -0
- package/dist/document-write-policy.d.ts.map +1 -1
- package/dist/document-write-policy.js +4 -2
- package/dist/document-write-policy.js.map +1 -1
- package/dist/domain-model.d.ts +73 -0
- package/dist/domain-model.d.ts.map +1 -0
- package/dist/domain-model.js +169 -0
- package/dist/domain-model.js.map +1 -0
- package/dist/engine.d.ts +21 -9
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +72 -14
- package/dist/engine.js.map +1 -1
- package/dist/file-journaled-backend-internal.d.ts +21 -0
- package/dist/file-journaled-backend-internal.d.ts.map +1 -0
- package/dist/file-journaled-backend-internal.js +12 -0
- package/dist/file-journaled-backend-internal.js.map +1 -0
- package/dist/file-journaled-backend.d.ts +160 -0
- package/dist/file-journaled-backend.d.ts.map +1 -0
- package/dist/file-journaled-backend.js +1345 -0
- package/dist/file-journaled-backend.js.map +1 -0
- package/dist/filesystem-host.d.ts +15 -0
- package/dist/filesystem-host.d.ts.map +1 -0
- package/dist/filesystem-host.js +20 -0
- package/dist/filesystem-host.js.map +1 -0
- package/dist/filesystem-identity.d.ts +97 -21
- package/dist/filesystem-identity.d.ts.map +1 -1
- package/dist/filesystem-identity.js +398 -119
- package/dist/filesystem-identity.js.map +1 -1
- package/dist/filesystem-lock.d.ts +16 -3
- package/dist/filesystem-lock.d.ts.map +1 -1
- package/dist/filesystem-lock.js +323 -77
- package/dist/filesystem-lock.js.map +1 -1
- package/dist/filesystem-push-role.d.ts +77 -0
- package/dist/filesystem-push-role.d.ts.map +1 -0
- package/dist/filesystem-push-role.js +165 -0
- package/dist/filesystem-push-role.js.map +1 -0
- package/dist/filesystem.d.ts +17 -0
- package/dist/filesystem.d.ts.map +1 -0
- package/dist/filesystem.js +18 -0
- package/dist/filesystem.js.map +1 -0
- package/dist/frontmatter.d.ts +8 -6
- package/dist/frontmatter.d.ts.map +1 -1
- package/dist/frontmatter.js +5 -4
- package/dist/frontmatter.js.map +1 -1
- package/dist/governed-body-write.d.ts +8 -1
- package/dist/governed-body-write.d.ts.map +1 -1
- package/dist/governed-body-write.js +9 -2
- package/dist/governed-body-write.js.map +1 -1
- package/dist/heads-digest.d.ts.map +1 -1
- package/dist/heads-digest.js +2 -1
- package/dist/heads-digest.js.map +1 -1
- package/dist/host-policy-snapshot.d.ts +9 -0
- package/dist/host-policy-snapshot.d.ts.map +1 -0
- package/dist/host-policy-snapshot.js +33 -0
- package/dist/host-policy-snapshot.js.map +1 -0
- package/dist/host-text.d.ts +31 -0
- package/dist/host-text.d.ts.map +1 -0
- package/dist/host-text.js +77 -0
- package/dist/host-text.js.map +1 -0
- package/dist/hosted-client/binding.d.ts +7 -0
- package/dist/hosted-client/binding.d.ts.map +1 -0
- package/dist/hosted-client/binding.js +10 -0
- package/dist/hosted-client/binding.js.map +1 -0
- package/dist/hosted-client/blob-delivery.d.ts +68 -0
- package/dist/hosted-client/blob-delivery.d.ts.map +1 -0
- package/dist/hosted-client/blob-delivery.js +170 -0
- package/dist/hosted-client/blob-delivery.js.map +1 -0
- package/dist/hosted-client/blob-write.d.ts +387 -0
- package/dist/hosted-client/blob-write.d.ts.map +1 -0
- package/dist/hosted-client/blob-write.js +770 -0
- package/dist/hosted-client/blob-write.js.map +1 -0
- package/dist/hosted-client/bundle-listing.d.ts +46 -0
- package/dist/hosted-client/bundle-listing.d.ts.map +1 -0
- package/dist/hosted-client/bundle-listing.js +36 -0
- package/dist/hosted-client/bundle-listing.js.map +1 -0
- package/dist/hosted-client/client.d.ts +219 -0
- package/dist/hosted-client/client.d.ts.map +1 -0
- package/dist/hosted-client/client.js +309 -0
- package/dist/hosted-client/client.js.map +1 -0
- package/dist/hosted-client/directory.d.ts +46 -0
- package/dist/hosted-client/directory.d.ts.map +1 -0
- package/dist/hosted-client/directory.js +74 -0
- package/dist/hosted-client/directory.js.map +1 -0
- package/dist/hosted-client/errors.d.ts +26 -0
- package/dist/hosted-client/errors.d.ts.map +1 -0
- package/dist/hosted-client/errors.js +35 -0
- package/dist/hosted-client/errors.js.map +1 -0
- package/dist/hosted-client/inbox.d.ts +320 -0
- package/dist/hosted-client/inbox.d.ts.map +1 -0
- package/dist/hosted-client/inbox.js +379 -0
- package/dist/hosted-client/inbox.js.map +1 -0
- package/dist/hosted-client/index.d.ts +30 -0
- package/dist/hosted-client/index.d.ts.map +1 -0
- package/dist/hosted-client/index.js +32 -0
- package/dist/hosted-client/index.js.map +1 -0
- package/dist/hosted-client/journal.d.ts +103 -0
- package/dist/hosted-client/journal.d.ts.map +1 -0
- package/dist/hosted-client/journal.js +464 -0
- package/dist/hosted-client/journal.js.map +1 -0
- package/dist/hosted-client/node.d.ts +74 -0
- package/dist/hosted-client/node.d.ts.map +1 -0
- package/dist/hosted-client/node.js +219 -0
- package/dist/hosted-client/node.js.map +1 -0
- package/dist/hosted-client/reference.d.ts +12 -0
- package/dist/hosted-client/reference.d.ts.map +1 -0
- package/dist/hosted-client/reference.js +29 -0
- package/dist/hosted-client/reference.js.map +1 -0
- package/dist/hosted-client/write.d.ts +217 -0
- package/dist/hosted-client/write.d.ts.map +1 -0
- package/dist/hosted-client/write.js +418 -0
- package/dist/hosted-client/write.js.map +1 -0
- package/dist/hosted-transport/answer-rows.d.ts +243 -0
- package/dist/hosted-transport/answer-rows.d.ts.map +1 -0
- package/dist/hosted-transport/answer-rows.js +457 -0
- package/dist/hosted-transport/answer-rows.js.map +1 -0
- package/dist/hosted-transport/blob-reads.d.ts +35 -0
- package/dist/hosted-transport/blob-reads.d.ts.map +1 -0
- package/dist/hosted-transport/blob-reads.js +179 -0
- package/dist/hosted-transport/blob-reads.js.map +1 -0
- package/dist/hosted-transport/blob-write.d.ts +34 -0
- package/dist/hosted-transport/blob-write.d.ts.map +1 -0
- package/dist/hosted-transport/blob-write.js +115 -0
- package/dist/hosted-transport/blob-write.js.map +1 -0
- package/dist/hosted-transport/carrier.d.ts +151 -0
- package/dist/hosted-transport/carrier.d.ts.map +1 -0
- package/dist/hosted-transport/carrier.js +260 -0
- package/dist/hosted-transport/carrier.js.map +1 -0
- package/dist/hosted-transport/document-backend.d.ts +74 -0
- package/dist/hosted-transport/document-backend.d.ts.map +1 -0
- package/dist/hosted-transport/document-backend.js +160 -0
- package/dist/hosted-transport/document-backend.js.map +1 -0
- package/dist/hosted-transport/history-pages.d.ts +55 -0
- package/dist/hosted-transport/history-pages.d.ts.map +1 -0
- package/dist/hosted-transport/history-pages.js +69 -0
- package/dist/hosted-transport/history-pages.js.map +1 -0
- package/dist/hosted-transport/index.d.ts +19 -0
- package/dist/hosted-transport/index.d.ts.map +1 -0
- package/dist/hosted-transport/index.js +19 -0
- package/dist/hosted-transport/index.js.map +1 -0
- package/dist/hosted-transport/operations.d.ts +85 -0
- package/dist/hosted-transport/operations.d.ts.map +1 -0
- package/dist/hosted-transport/operations.js +128 -0
- package/dist/hosted-transport/operations.js.map +1 -0
- package/dist/hosted-transport/paged-reads.d.ts +87 -0
- package/dist/hosted-transport/paged-reads.d.ts.map +1 -0
- package/dist/hosted-transport/paged-reads.js +238 -0
- package/dist/hosted-transport/paged-reads.js.map +1 -0
- package/dist/hosted-transport/read-adapter.d.ts +256 -0
- package/dist/hosted-transport/read-adapter.d.ts.map +1 -0
- package/dist/hosted-transport/read-adapter.js +568 -0
- package/dist/hosted-transport/read-adapter.js.map +1 -0
- package/dist/hosted-transport/root-write.d.ts +114 -0
- package/dist/hosted-transport/root-write.d.ts.map +1 -0
- package/dist/hosted-transport/root-write.js +171 -0
- package/dist/hosted-transport/root-write.js.map +1 -0
- package/dist/hosted-transport/whole-document-transport.d.ts +204 -0
- package/dist/hosted-transport/whole-document-transport.d.ts.map +1 -0
- package/dist/hosted-transport/whole-document-transport.js +447 -0
- package/dist/hosted-transport/whole-document-transport.js.map +1 -0
- package/dist/identity-fold.d.ts +18 -0
- package/dist/identity-fold.d.ts.map +1 -0
- package/dist/identity-fold.js +22 -0
- package/dist/identity-fold.js.map +1 -0
- package/dist/index.d.ts +41 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -9
- package/dist/index.js.map +1 -1
- package/dist/indexeddb-backend.d.ts +25 -33
- package/dist/indexeddb-backend.d.ts.map +1 -1
- package/dist/indexeddb-backend.js +264 -8
- package/dist/indexeddb-backend.js.map +1 -1
- package/dist/inventory-guard.d.ts +58 -0
- package/dist/inventory-guard.d.ts.map +1 -0
- package/dist/inventory-guard.js +169 -0
- package/dist/inventory-guard.js.map +1 -0
- package/dist/journaled-backend.d.ts +149 -9
- package/dist/journaled-backend.d.ts.map +1 -1
- package/dist/journaled-backend.js +64 -58
- package/dist/journaled-backend.js.map +1 -1
- package/dist/kind-authoring.d.ts +170 -0
- package/dist/kind-authoring.d.ts.map +1 -0
- package/dist/kind-authoring.js +452 -0
- package/dist/kind-authoring.js.map +1 -0
- package/dist/kind-changes.d.ts +45 -0
- package/dist/kind-changes.d.ts.map +1 -0
- package/dist/kind-changes.js +96 -0
- package/dist/kind-changes.js.map +1 -0
- package/dist/kind-conformance-error.d.ts +15 -0
- package/dist/kind-conformance-error.d.ts.map +1 -0
- package/dist/kind-conformance-error.js +20 -0
- package/dist/kind-conformance-error.js.map +1 -0
- package/dist/kind-coverage.d.ts +99 -0
- package/dist/kind-coverage.d.ts.map +1 -0
- package/dist/kind-coverage.js +186 -0
- package/dist/kind-coverage.js.map +1 -0
- package/dist/kind-deprecation.d.ts +32 -0
- package/dist/kind-deprecation.d.ts.map +1 -0
- package/dist/kind-deprecation.js +141 -0
- package/dist/kind-deprecation.js.map +1 -0
- package/dist/kind-field-mutation.d.ts +4 -0
- package/dist/kind-field-mutation.d.ts.map +1 -1
- package/dist/kind-field-mutation.js +28 -3
- package/dist/kind-field-mutation.js.map +1 -1
- package/dist/kinds-load.js +4 -4
- package/dist/kinds-load.js.map +1 -1
- package/dist/kinds.d.ts +111 -2
- package/dist/kinds.d.ts.map +1 -1
- package/dist/kinds.js +386 -20
- package/dist/kinds.js.map +1 -1
- package/dist/link-emit.d.ts +29 -0
- package/dist/link-emit.d.ts.map +1 -0
- package/dist/link-emit.js +39 -0
- package/dist/link-emit.js.map +1 -0
- package/dist/link-types.d.ts +27 -0
- package/dist/link-types.d.ts.map +1 -0
- package/dist/link-types.js +60 -0
- package/dist/link-types.js.map +1 -0
- package/dist/links.d.ts +52 -3
- package/dist/links.d.ts.map +1 -1
- package/dist/links.js +259 -18
- package/dist/links.js.map +1 -1
- package/dist/managed-document-metadata.d.ts +2 -0
- package/dist/managed-document-metadata.d.ts.map +1 -0
- package/dist/managed-document-metadata.js +9 -0
- package/dist/managed-document-metadata.js.map +1 -0
- package/dist/memory-backend.d.ts +3 -0
- package/dist/memory-backend.d.ts.map +1 -1
- package/dist/memory-backend.js +44 -2
- package/dist/memory-backend.js.map +1 -1
- package/dist/okf-actor.d.ts +1 -0
- package/dist/okf-actor.d.ts.map +1 -1
- package/dist/okf-actor.js +1 -0
- package/dist/okf-actor.js.map +1 -1
- package/dist/okf-authoring.d.ts +4 -0
- package/dist/okf-authoring.d.ts.map +1 -0
- package/dist/okf-authoring.js +12 -0
- package/dist/okf-authoring.js.map +1 -0
- package/dist/okf-standard-fields.d.ts +3 -0
- package/dist/okf-standard-fields.d.ts.map +1 -1
- package/dist/okf-standard-fields.js +3 -0
- package/dist/okf-standard-fields.js.map +1 -1
- package/dist/path-fold.d.ts +39 -0
- package/dist/path-fold.d.ts.map +1 -0
- package/dist/path-fold.js +41 -0
- package/dist/path-fold.js.map +1 -0
- package/dist/paths.d.ts +18 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +32 -0
- package/dist/paths.js.map +1 -1
- package/dist/platform.d.ts +13 -2
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +16 -0
- package/dist/platform.js.map +1 -1
- package/dist/portable-export.d.ts +119 -0
- package/dist/portable-export.d.ts.map +1 -0
- package/dist/portable-export.js +501 -0
- package/dist/portable-export.js.map +1 -0
- package/dist/publication-filesystem.d.ts +1 -1
- package/dist/publication-filesystem.d.ts.map +1 -1
- package/dist/publication-filesystem.js +1 -1
- package/dist/publication-filesystem.js.map +1 -1
- package/dist/qualified-links.d.ts +67 -0
- package/dist/qualified-links.d.ts.map +1 -0
- package/dist/qualified-links.js +117 -0
- package/dist/qualified-links.js.map +1 -0
- package/dist/query-filter.d.ts +16 -2
- package/dist/query-filter.d.ts.map +1 -1
- package/dist/query-filter.js +40 -1
- package/dist/query-filter.js.map +1 -1
- package/dist/query-order.d.ts +22 -0
- package/dist/query-order.d.ts.map +1 -0
- package/dist/query-order.js +43 -0
- package/dist/query-order.js.map +1 -0
- package/dist/recipe-lifecycle/errors.d.ts +96 -0
- package/dist/recipe-lifecycle/errors.d.ts.map +1 -0
- package/dist/recipe-lifecycle/errors.js +122 -0
- package/dist/recipe-lifecycle/errors.js.map +1 -0
- package/dist/recipe-lifecycle/evolution.d.ts +124 -0
- package/dist/recipe-lifecycle/evolution.d.ts.map +1 -0
- package/dist/recipe-lifecycle/evolution.js +993 -0
- package/dist/recipe-lifecycle/evolution.js.map +1 -0
- package/dist/recipe-lifecycle/index.d.ts +34 -0
- package/dist/recipe-lifecycle/index.d.ts.map +1 -0
- package/dist/recipe-lifecycle/index.js +28 -0
- package/dist/recipe-lifecycle/index.js.map +1 -0
- package/dist/recipe-lifecycle/install.d.ts +155 -0
- package/dist/recipe-lifecycle/install.d.ts.map +1 -0
- package/dist/recipe-lifecycle/install.js +604 -0
- package/dist/recipe-lifecycle/install.js.map +1 -0
- package/dist/recipe-lifecycle/legacy-aliases.d.ts +16 -0
- package/dist/recipe-lifecycle/legacy-aliases.d.ts.map +1 -0
- package/dist/recipe-lifecycle/legacy-aliases.js +51 -0
- package/dist/recipe-lifecycle/legacy-aliases.js.map +1 -0
- package/dist/recipe-lifecycle/retirement-apply.d.ts +59 -0
- package/dist/recipe-lifecycle/retirement-apply.d.ts.map +1 -0
- package/dist/recipe-lifecycle/retirement-apply.js +165 -0
- package/dist/recipe-lifecycle/retirement-apply.js.map +1 -0
- package/dist/recipe-lifecycle/retirement.d.ts +89 -0
- package/dist/recipe-lifecycle/retirement.d.ts.map +1 -0
- package/dist/recipe-lifecycle/retirement.js +347 -0
- package/dist/recipe-lifecycle/retirement.js.map +1 -0
- package/dist/recipe-lifecycle/stamp.d.ts +151 -0
- package/dist/recipe-lifecycle/stamp.d.ts.map +1 -0
- package/dist/recipe-lifecycle/stamp.js +364 -0
- package/dist/recipe-lifecycle/stamp.js.map +1 -0
- package/dist/recipe-set-preflight.d.ts +139 -0
- package/dist/recipe-set-preflight.d.ts.map +1 -0
- package/dist/recipe-set-preflight.js +536 -0
- package/dist/recipe-set-preflight.js.map +1 -0
- package/dist/recipes.d.ts +29 -1
- package/dist/recipes.d.ts.map +1 -1
- package/dist/recipes.js +137 -1
- package/dist/recipes.js.map +1 -1
- package/dist/record-overlay/apply.d.ts +43 -0
- package/dist/record-overlay/apply.d.ts.map +1 -0
- package/dist/record-overlay/apply.js +527 -0
- package/dist/record-overlay/apply.js.map +1 -0
- package/dist/record-overlay/canonical.d.ts +22 -0
- package/dist/record-overlay/canonical.d.ts.map +1 -0
- package/dist/record-overlay/canonical.js +47 -0
- package/dist/record-overlay/canonical.js.map +1 -0
- package/dist/record-overlay/digest.d.ts +4 -0
- package/dist/record-overlay/digest.d.ts.map +1 -0
- package/dist/record-overlay/digest.js +11 -0
- package/dist/record-overlay/digest.js.map +1 -0
- package/dist/record-overlay/errors.d.ts +44 -0
- package/dist/record-overlay/errors.d.ts.map +1 -0
- package/dist/record-overlay/errors.js +55 -0
- package/dist/record-overlay/errors.js.map +1 -0
- package/dist/record-overlay/findings.d.ts +5 -0
- package/dist/record-overlay/findings.d.ts.map +1 -0
- package/dist/record-overlay/findings.js +83 -0
- package/dist/record-overlay/findings.js.map +1 -0
- package/dist/record-overlay/index.d.ts +23 -0
- package/dist/record-overlay/index.d.ts.map +1 -0
- package/dist/record-overlay/index.js +21 -0
- package/dist/record-overlay/index.js.map +1 -0
- package/dist/record-overlay/plan.d.ts +12 -0
- package/dist/record-overlay/plan.d.ts.map +1 -0
- package/dist/record-overlay/plan.js +285 -0
- package/dist/record-overlay/plan.js.map +1 -0
- package/dist/record-overlay/policy.d.ts +74 -0
- package/dist/record-overlay/policy.d.ts.map +1 -0
- package/dist/record-overlay/policy.js +235 -0
- package/dist/record-overlay/policy.js.map +1 -0
- package/dist/record-overlay/redact.d.ts +3 -0
- package/dist/record-overlay/redact.d.ts.map +1 -0
- package/dist/record-overlay/redact.js +27 -0
- package/dist/record-overlay/redact.js.map +1 -0
- package/dist/record-overlay/signal-kind.d.ts +30 -0
- package/dist/record-overlay/signal-kind.d.ts.map +1 -0
- package/dist/record-overlay/signal-kind.js +38 -0
- package/dist/record-overlay/signal-kind.js.map +1 -0
- package/dist/record-overlay/signal-text.d.ts +45 -0
- package/dist/record-overlay/signal-text.d.ts.map +1 -0
- package/dist/record-overlay/signal-text.js +116 -0
- package/dist/record-overlay/signal-text.js.map +1 -0
- package/dist/record-overlay/source-index.d.ts +19 -0
- package/dist/record-overlay/source-index.d.ts.map +1 -0
- package/dist/record-overlay/source-index.js +102 -0
- package/dist/record-overlay/source-index.js.map +1 -0
- package/dist/record-overlay/types.d.ts +291 -0
- package/dist/record-overlay/types.d.ts.map +1 -0
- package/dist/record-overlay/types.js +2 -0
- package/dist/record-overlay/types.js.map +1 -0
- package/dist/remote-backend.d.ts +58 -13
- package/dist/remote-backend.d.ts.map +1 -1
- package/dist/remote-backend.js +156 -36
- package/dist/remote-backend.js.map +1 -1
- package/dist/remote-document-codec.d.ts +2 -1
- package/dist/remote-document-codec.d.ts.map +1 -1
- package/dist/remote-document-codec.js +3 -2
- package/dist/remote-document-codec.js.map +1 -1
- package/dist/remote-error.d.ts +17 -1
- package/dist/remote-error.d.ts.map +1 -1
- package/dist/remote-error.js +32 -2
- package/dist/remote-error.js.map +1 -1
- package/dist/remote-operations.d.ts +0 -40
- package/dist/remote-operations.d.ts.map +1 -1
- package/dist/remote-operations.js +26 -6
- package/dist/remote-operations.js.map +1 -1
- package/dist/remote-parsers.d.ts +1 -1
- package/dist/remote-parsers.js +1 -1
- package/dist/search/backend-source.d.ts +6 -0
- package/dist/search/backend-source.d.ts.map +1 -0
- package/dist/search/backend-source.js +33 -0
- package/dist/search/backend-source.js.map +1 -0
- package/dist/search/contract.d.ts +151 -0
- package/dist/search/contract.d.ts.map +1 -0
- package/dist/search/contract.js +78 -0
- package/dist/search/contract.js.map +1 -0
- package/dist/search/errors.d.ts +30 -0
- package/dist/search/errors.d.ts.map +1 -0
- package/dist/search/errors.js +50 -0
- package/dist/search/errors.js.map +1 -0
- package/dist/search/identity.d.ts +12 -0
- package/dist/search/identity.d.ts.map +1 -0
- package/dist/search/identity.js +90 -0
- package/dist/search/identity.js.map +1 -0
- package/dist/search/index.d.ts +16 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +15 -0
- package/dist/search/index.js.map +1 -0
- package/dist/search/memory-source.d.ts +36 -0
- package/dist/search/memory-source.d.ts.map +1 -0
- package/dist/search/memory-source.js +235 -0
- package/dist/search/memory-source.js.map +1 -0
- package/dist/search/rank.d.ts +34 -0
- package/dist/search/rank.d.ts.map +1 -0
- package/dist/search/rank.js +129 -0
- package/dist/search/rank.js.map +1 -0
- package/dist/search/row-text.d.ts +38 -0
- package/dist/search/row-text.d.ts.map +1 -0
- package/dist/search/row-text.js +88 -0
- package/dist/search/row-text.js.map +1 -0
- package/dist/search/search.d.ts +28 -0
- package/dist/search/search.d.ts.map +1 -0
- package/dist/search/search.js +257 -0
- package/dist/search/search.js.map +1 -0
- package/dist/search/source.d.ts +67 -0
- package/dist/search/source.d.ts.map +1 -0
- package/dist/search/source.js +2 -0
- package/dist/search/source.js.map +1 -0
- package/dist/search/text.d.ts +47 -0
- package/dist/search/text.d.ts.map +1 -0
- package/dist/search/text.js +150 -0
- package/dist/search/text.js.map +1 -0
- package/dist/site-capabilities.d.ts +345 -0
- package/dist/site-capabilities.d.ts.map +1 -0
- package/dist/site-capabilities.js +724 -0
- package/dist/site-capabilities.js.map +1 -0
- package/dist/source-engine/driver.d.ts +42 -0
- package/dist/source-engine/driver.d.ts.map +1 -0
- package/dist/source-engine/driver.js +529 -0
- package/dist/source-engine/driver.js.map +1 -0
- package/dist/source-engine/evidence.d.ts +39 -0
- package/dist/source-engine/evidence.d.ts.map +1 -0
- package/dist/source-engine/evidence.js +54 -0
- package/dist/source-engine/evidence.js.map +1 -0
- package/dist/source-engine/guard.d.ts +87 -0
- package/dist/source-engine/guard.d.ts.map +1 -0
- package/dist/source-engine/guard.js +427 -0
- package/dist/source-engine/guard.js.map +1 -0
- package/dist/source-engine/index.d.ts +35 -0
- package/dist/source-engine/index.d.ts.map +1 -0
- package/dist/source-engine/index.js +27 -0
- package/dist/source-engine/index.js.map +1 -0
- package/dist/source-engine/machine-state.d.ts +87 -0
- package/dist/source-engine/machine-state.d.ts.map +1 -0
- package/dist/source-engine/machine-state.js +94 -0
- package/dist/source-engine/machine-state.js.map +1 -0
- package/dist/source-engine/observe.d.ts +10 -0
- package/dist/source-engine/observe.d.ts.map +1 -0
- package/dist/source-engine/observe.js +276 -0
- package/dist/source-engine/observe.js.map +1 -0
- package/dist/source-engine/overlay.d.ts +50 -0
- package/dist/source-engine/overlay.d.ts.map +1 -0
- package/dist/source-engine/overlay.js +114 -0
- package/dist/source-engine/overlay.js.map +1 -0
- package/dist/source-engine/robots.d.ts +53 -0
- package/dist/source-engine/robots.d.ts.map +1 -0
- package/dist/source-engine/robots.js +158 -0
- package/dist/source-engine/robots.js.map +1 -0
- package/dist/source-engine/snapshot.d.ts +104 -0
- package/dist/source-engine/snapshot.d.ts.map +1 -0
- package/dist/source-engine/snapshot.js +240 -0
- package/dist/source-engine/snapshot.js.map +1 -0
- package/dist/source-engine/target-consumer.d.ts +12 -0
- package/dist/source-engine/target-consumer.d.ts.map +1 -0
- package/dist/source-engine/target-consumer.js +15 -0
- package/dist/source-engine/target-consumer.js.map +1 -0
- package/dist/source-engine/target-descriptor.d.ts +3 -0
- package/dist/source-engine/target-descriptor.d.ts.map +1 -0
- package/dist/source-engine/target-descriptor.js +26 -0
- package/dist/source-engine/target-descriptor.js.map +1 -0
- package/dist/source-engine/target.d.ts +82 -0
- package/dist/source-engine/target.d.ts.map +1 -0
- package/dist/source-engine/target.js +213 -0
- package/dist/source-engine/target.js.map +1 -0
- package/dist/source-engine/types.d.ts +296 -0
- package/dist/source-engine/types.d.ts.map +1 -0
- package/dist/source-engine/types.js +2 -0
- package/dist/source-engine/types.js.map +1 -0
- package/dist/source-harness/conformance.d.ts +16 -0
- package/dist/source-harness/conformance.d.ts.map +1 -0
- package/dist/source-harness/conformance.js +266 -0
- package/dist/source-harness/conformance.js.map +1 -0
- package/dist/source-harness/fixture.d.ts +97 -0
- package/dist/source-harness/fixture.d.ts.map +1 -0
- package/dist/source-harness/fixture.js +276 -0
- package/dist/source-harness/fixture.js.map +1 -0
- package/dist/source-harness/index.d.ts +22 -0
- package/dist/source-harness/index.d.ts.map +1 -0
- package/dist/source-harness/index.js +19 -0
- package/dist/source-harness/index.js.map +1 -0
- package/dist/source-harness/peer.d.ts +74 -0
- package/dist/source-harness/peer.d.ts.map +1 -0
- package/dist/source-harness/peer.js +106 -0
- package/dist/source-harness/peer.js.map +1 -0
- package/dist/source-profiles/index.d.ts +31 -0
- package/dist/source-profiles/index.d.ts.map +1 -0
- package/dist/source-profiles/index.js +206 -0
- package/dist/source-profiles/index.js.map +1 -0
- package/dist/source-profiles/link-header.d.ts +6 -0
- package/dist/source-profiles/link-header.d.ts.map +1 -0
- package/dist/source-profiles/link-header.js +131 -0
- package/dist/source-profiles/link-header.js.map +1 -0
- package/dist/storage.d.ts +9 -2
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +6 -1
- package/dist/storage.js.map +1 -1
- package/dist/string-identity-agreement.d.ts +35 -0
- package/dist/string-identity-agreement.d.ts.map +1 -0
- package/dist/string-identity-agreement.js +72 -0
- package/dist/string-identity-agreement.js.map +1 -0
- package/dist/types.d.ts +41 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -10
- package/dist/types.js.map +1 -1
- package/dist/uncertain-write.d.ts +157 -17
- package/dist/uncertain-write.d.ts.map +1 -1
- package/dist/uncertain-write.js +86 -30
- package/dist/uncertain-write.js.map +1 -1
- package/dist/versioning.d.ts +9 -4
- package/dist/versioning.d.ts.map +1 -1
- package/dist/versioning.js +9 -4
- package/dist/versioning.js.map +1 -1
- package/dist/view-launch-contract.d.ts +14 -0
- package/dist/view-launch-contract.d.ts.map +1 -0
- package/dist/view-launch-contract.js +23 -0
- package/dist/view-launch-contract.js.map +1 -0
- package/dist/write-identity.d.ts +5 -0
- package/dist/write-identity.d.ts.map +1 -0
- package/dist/write-identity.js +9 -0
- package/dist/write-identity.js.map +1 -0
- package/package.json +140 -3
package/README.md
ADDED
|
@@ -0,0 +1,1255 @@
|
|
|
1
|
+
# @superbee/core
|
|
2
|
+
|
|
3
|
+
The Superbee knowledge engine reads and writes Open Knowledge Format bundles, with versioned
|
|
4
|
+
writes, cross-links and storage backends. It is a library; installing it does not install the
|
|
5
|
+
`superbee` executable.
|
|
6
|
+
|
|
7
|
+
Core and `@superbee/server` are released together, independently of the `superbee` CLI. A CLI
|
|
8
|
+
release bundles the engine source selected at build time; it does not load `@superbee/core@latest`.
|
|
9
|
+
|
|
10
|
+
Browser and Worker consumers can compose existing operation helpers through
|
|
11
|
+
`@superbee/core/bundle-ops` (including the `Bundle` type), `@superbee/core/kinds`
|
|
12
|
+
(including `ValidationWarning`), `@superbee/core/kinds-load`,
|
|
13
|
+
`@superbee/core/document-field-actions`, and `@superbee/core/okf-actor` (including the shared
|
|
14
|
+
`OkfActorError`). `@superbee/core/okf-authoring` owns the supported authoring versions, default
|
|
15
|
+
version, and resolver; the Node root retains the same exports. Supply an explicit backend to
|
|
16
|
+
bundle-shaped operations in browser hosts.
|
|
17
|
+
|
|
18
|
+
## Identity ordering
|
|
19
|
+
|
|
20
|
+
Engine list/query/head and edge results, View identity lists, and new publication captures use
|
|
21
|
+
Core's UTF-16 code-unit comparator. This corrects the earlier local locale-dependent order and
|
|
22
|
+
can change capped query results and fresh publication snapshot digests. Existing publication
|
|
23
|
+
snapshots retain their serialized bytes and identities. See the [ordering compatibility note](../publication/ORDERING.md)
|
|
24
|
+
for cursor, envelope and snapshot-version compatibility.
|
|
25
|
+
|
|
26
|
+
## Kind declaration changes
|
|
27
|
+
|
|
28
|
+
`@superbee/core/kind-changes` exports `classifyKindChanges(current, desired)` over parsed
|
|
29
|
+
`KindConvention` values. It reports required/enum/section, relationship, field-type and terminal
|
|
30
|
+
changes without deciding whether to approve them. Arrays preserve declaration occurrence order;
|
|
31
|
+
terminal flags distinguish ordered values from occurrence counts. The helper never mutates inputs.
|
|
32
|
+
Recipe ownership, additive merge, instance/edge proofs and host representation bounds remain the
|
|
33
|
+
caller's policy. These facts do not establish general document equality or authorize a write.
|
|
34
|
+
|
|
35
|
+
## Stored document decoding
|
|
36
|
+
|
|
37
|
+
`@superbee/core/document-codec` exports `parseMarkdown(raw, context, options)` and
|
|
38
|
+
`parseFrontmatter(yaml, context, options)` with the shared `ParseDocumentOptions` type.
|
|
39
|
+
Use `{ preserveTimestamp: true }` when reading stored values: the legacy top-level `timestamp`
|
|
40
|
+
keeps its parsed number or original string, independently of the bundle edition. The default
|
|
41
|
+
and explicit v0.1 behavior still normalize that field; v0.2 preserves it as before. Neither
|
|
42
|
+
option changes body splitting or attributed `MalformedDocumentError` identity.
|
|
43
|
+
|
|
44
|
+
This preserves parsed values, not YAML formatting or original document bytes after serialization.
|
|
45
|
+
Backends remain responsible for retaining raw bytes and deriving their revision tokens.
|
|
46
|
+
|
|
47
|
+
## Document presentation values
|
|
48
|
+
|
|
49
|
+
The additive `@superbee/core/artifact-contract` subpath provides pure v1 document target,
|
|
50
|
+
observation, request, receipt and capability decoders. See the [contract reference](https://github.com/Holaxis-ai/superbee/blob/main/packages/core/ARTIFACT-CONTRACT.md)
|
|
51
|
+
for identity, provenance, lifecycle and host responsibilities.
|
|
52
|
+
|
|
53
|
+
## Site capabilities
|
|
54
|
+
|
|
55
|
+
The additive `@superbee/core/site-capabilities` subpath is the contract for read-only tools a
|
|
56
|
+
site or a recipe pack provides (`SiteCapability`), and the one owner of their composition rules:
|
|
57
|
+
`composeSiteCapabilities` refuses duplicate names, `superbee_` names outside the generic tier and
|
|
58
|
+
names outside a pack's or site's namespace, orders generic, pack and site tools deterministically,
|
|
59
|
+
applies a surface's exclusions after those checks, enforces size limits and, with
|
|
60
|
+
`requireReadOnly`, refuses any capability not declared read-only. It returns frozen snapshots and
|
|
61
|
+
an identity digest for the surface's list. `toProviderTool`, `toPortalAdditionalTool`,
|
|
62
|
+
`toToolDescriptor` and `siteCapabilityFromPortalTool` are the explicit mappings to the existing
|
|
63
|
+
tool shapes. `checkSiteCapability` and `siteCapabilityConformanceCases` test a capability against
|
|
64
|
+
recorded fixtures (read-only declaration, dated results, closed error codes that never echo input,
|
|
65
|
+
bounded and deterministic output, same-origin URLs, citations of records the result contains, and
|
|
66
|
+
refusal of unknown arguments). `siteRecordSource` mints a `site-record` citation only for a path
|
|
67
|
+
the host admits, bound to the publication's digests.
|
|
68
|
+
|
|
69
|
+
Annotations such as `readOnlyHint` and `untrustedContentHint` are self-declared and enforce
|
|
70
|
+
nothing at runtime; the checks above test recorded fixtures, not the code. Treat them as a control
|
|
71
|
+
only for in-process code from the same reviewed deploy. The module has no Node built-in import, so
|
|
72
|
+
it runs unchanged in a Worker or a browser.
|
|
73
|
+
|
|
74
|
+
## Mutation attribution and local history
|
|
75
|
+
|
|
76
|
+
`mutateDocument` and `prepareDocumentMutationCandidate` persist a supplied `actor` by default:
|
|
77
|
+
`actor` in OKF v0.1, `superbee_updated_by` in v0.2 (plus `actor` when the Kind requires it).
|
|
78
|
+
Use `persistActor: false` to keep candidate attribution unchanged, or `persistActor: true` to apply
|
|
79
|
+
the attribution policy even without a supplied actor. Supplying the actor option alone leaves an
|
|
80
|
+
otherwise unchanged candidate a no-op, preserving its original attribution and bytes. In v0.1,
|
|
81
|
+
deliberate edits to the candidate's legacy `actor` field still count as authored changes in `patch`
|
|
82
|
+
and `replace-document` modes; overwrite retains its automatic attribution comparison. The pure preview
|
|
83
|
+
prepares patches. This default changes library writes that previously
|
|
84
|
+
required `persistActor: true`; callers needing the old opt-in behavior can explicitly pass false.
|
|
85
|
+
|
|
86
|
+
`producer` controls `generated.by` independently of the version mutator. Local filesystem history
|
|
87
|
+
returns only the current revision, including for Git-tracked bundles. It reads mutation attribution
|
|
88
|
+
from `superbee_updated_by`, legacy `updated_by`, or `actor`; when absent it returns the stable string
|
|
89
|
+
`unattributed`. It does not infer an identity from the reader's environment or the content producer.
|
|
90
|
+
History-keeping backends retain their existing per-write attribution.
|
|
91
|
+
|
|
92
|
+
## Filesystem platform migration
|
|
93
|
+
|
|
94
|
+
The Windows extraction changes the default Node.js filesystem backend. Its built-in host policy
|
|
95
|
+
supports macOS and Linux. On native Windows or another unsupported host, filesystem construction
|
|
96
|
+
requires an explicit `FilesystemHostPolicy`; there is no automatic Windows fallback.
|
|
97
|
+
|
|
98
|
+
| Entry point | Default on macOS/Linux | On other hosts |
|
|
99
|
+
| --- | --- | --- |
|
|
100
|
+
| `new FilesystemBackend(root)` | Uses the built-in policy | Pass `{ hostPolicy }` as the second argument |
|
|
101
|
+
| `initBundle(root, options)` | Uses the built-in policy | Pass `{ hostPolicy }` as the third argument |
|
|
102
|
+
| `createFilesystemRuntime()` | Captures the built-in policy | Pass a host policy as its argument |
|
|
103
|
+
| `withFilesystemMutationLock(target, body, options)` | Uses the built-in policy | Include `hostPolicy` in `options` |
|
|
104
|
+
| `filesystemMutationLockPath(target, portableRoot)` | Uses the built-in policy | Pass a host policy as the third argument |
|
|
105
|
+
|
|
106
|
+
Without that policy, these filesystem entry points throw `InvalidInputError` on unsupported hosts;
|
|
107
|
+
backend construction and bundle initialization refuse before creating or modifying the target.
|
|
108
|
+
The package has no npm `os` restriction: storage contracts and non-filesystem backends remain
|
|
109
|
+
available independently of the default filesystem policy. Choose a backend and imports appropriate
|
|
110
|
+
to your runtime; installing the library alone does not establish native filesystem support.
|
|
111
|
+
|
|
112
|
+
There is no Windows adapter on npm to install. The experimental first-party adapters are built
|
|
113
|
+
from source in a separate repository, https://github.com/Holaxis-ai/superbee-windows-cli.
|
|
114
|
+
Supplying a policy is an integration contract for an adapter owner, not a promise that arbitrary
|
|
115
|
+
policies make Windows supported.
|
|
116
|
+
|
|
117
|
+
## Keep the backend returned by initBundle
|
|
118
|
+
|
|
119
|
+
`initBundle` now returns `{ root, backend }`. Pass that returned bundle to engine operations so they
|
|
120
|
+
retain the backend and host policy selected during initialization:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { initBundle, readDoc, writeDoc } from '@superbee/core';
|
|
124
|
+
|
|
125
|
+
const bundle = await initBundle('./knowledge'); // Default filesystem: macOS/Linux.
|
|
126
|
+
await writeDoc(bundle, {
|
|
127
|
+
id: 'notes/example',
|
|
128
|
+
frontmatter: { type: 'Note', title: 'Example' },
|
|
129
|
+
body: 'Shared knowledge.\n',
|
|
130
|
+
});
|
|
131
|
+
const document = await readDoc(bundle, 'notes/example');
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Reading `bundle.root` still works. Code that assumes the return value has exactly one property,
|
|
135
|
+
serializes the whole return value, or uses backend presence as a synonym for a remote bundle must
|
|
136
|
+
be updated. A filesystem bundle now has a backend too. Serialize only the fields your application
|
|
137
|
+
intends to persist; do not reconstruct `{ root: bundle.root }` when continuing engine operations
|
|
138
|
+
that need the selected backend.
|
|
139
|
+
|
|
140
|
+
## Integrating an explicit filesystem policy
|
|
141
|
+
|
|
142
|
+
`@superbee/core/filesystem` exports `createFilesystemRuntime`, `FilesystemBackend`, and the
|
|
143
|
+
`FilesystemHostPolicy` type. An application that owns its host adapter can bind it once:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import { createFilesystemRuntime, type FilesystemHostPolicy } from '@superbee/core/filesystem';
|
|
147
|
+
|
|
148
|
+
export async function openFilesystemBundle(root: string, hostPolicy: FilesystemHostPolicy) {
|
|
149
|
+
const filesystem = createFilesystemRuntime(hostPolicy);
|
|
150
|
+
return filesystem.initBundle(root);
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The resulting runtime supplies `backend`, `initBundle`, `withMutationLock`, and `mutationLockPath`
|
|
155
|
+
with the same captured policy. Direct backend construction uses
|
|
156
|
+
`new FilesystemBackend(root, { hostPolicy })`. Lock helpers are also exported from `@superbee/core`.
|
|
157
|
+
|
|
158
|
+
A policy supplies the lock-parent directory, owner-namespace key, private-mode enforcement choice,
|
|
159
|
+
and host error classifications. Core still owns lock ownership, version checks, retry bounds and
|
|
160
|
+
cleanup. Policies are structural snapshots: public data and methods are captured, while callbacks
|
|
161
|
+
may continue to observe live operating-system facts. Do not use opaque private fields or methods
|
|
162
|
+
pre-bound to a mutable receiver.
|
|
163
|
+
|
|
164
|
+
## Recipe lifecycle
|
|
165
|
+
|
|
166
|
+
`@superbee/core/recipe-lifecycle` installs and evolves a parsed recipe on any bundle. The caller
|
|
167
|
+
acquires the recipe (`parseRecipeFiles` from `@superbee/core/recipes` turns files into a
|
|
168
|
+
`LoadedRecipe`) and presents the results; the `superbee` CLI is one such caller. It uses no Node
|
|
169
|
+
globals or builtins, so it runs in Node, a browser or a Worker over any storage backend.
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
import { MemoryBackend } from "@superbee/core/memory-backend";
|
|
173
|
+
import { parseRecipeFiles } from "@superbee/core/recipes";
|
|
174
|
+
import { applyRecipeEvolution, installRecipe, planRecipeEvolution } from "@superbee/core/recipe-lifecycle";
|
|
175
|
+
|
|
176
|
+
const bundle = { root: "memory://glossary", backend: new MemoryBackend() };
|
|
177
|
+
const v1 = parseRecipeFiles(glossaryV1Files, "memory:glossary@1");
|
|
178
|
+
if (!v1.ok) throw new Error(v1.error.message);
|
|
179
|
+
await installRecipe(bundle, v1.recipe);
|
|
180
|
+
|
|
181
|
+
const v2 = parseRecipeFiles(glossaryV2Files, "memory:glossary@2");
|
|
182
|
+
if (!v2.ok) throw new Error(v2.error.message);
|
|
183
|
+
const plan = await planRecipeEvolution(bundle, v2.recipe);
|
|
184
|
+
if (plan.ready && plan.writes > 0) {
|
|
185
|
+
await applyRecipeEvolution(bundle, v2.recipe, plan.plan_token, { actor: "process:glossary-sync" });
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`prepareRecipeInstall(recipe, { okfVersion, now })` prepares those same edition-specific candidates
|
|
190
|
+
without a backend or target reads. Retain the explicit `now` across preview and replay; v0.1 uses
|
|
191
|
+
it for the existing install timestamp, while v0.2 adds no legacy clock. The returned `identity`
|
|
192
|
+
contains the source digest taken before materialization; Convention, Connector and Reference
|
|
193
|
+
candidates already carry their recipe stamps. Preparation does not mutate the parsed source.
|
|
194
|
+
|
|
195
|
+
`inspectRecipeInstallTarget(existingOrNull, candidate, prepared)` separately reports existence,
|
|
196
|
+
source-content equality, recipe stamp identity and stamp integrity. For a matching installed
|
|
197
|
+
artifact, require `sameContent`, `stamp: "current"` and `stampIntegrity: "current"`; an identity match
|
|
198
|
+
alone does not establish readable digest rules or an intact definition stamp. Neither function
|
|
199
|
+
installs, adopts stamps, validates the target registry or grants write authority. An admitted host
|
|
200
|
+
can use the candidates with its existing canonical writer after its own coherent target and
|
|
201
|
+
compatibility checks; preparation is not an approved-preview transaction or a new apply API.
|
|
202
|
+
|
|
203
|
+
`installRecipe` is create-only: each convention, Connector, View pair and Reference is written with
|
|
204
|
+
an expect-absent compare-and-swap, an existing convention or Connector is left untouched (its row
|
|
205
|
+
reports `source_differs` when it differs from the recipe), and a differing View or Reference target
|
|
206
|
+
is refused before anything is written. Before any write it checks each Connector against the
|
|
207
|
+
authoring rules of the bundle's edition (`RecipeInputError` when OKF v0.2 would refuse it) and its
|
|
208
|
+
sources against the Kinds the bundle will hold (its conventions plus the recipe's own), throwing
|
|
209
|
+
`ConnectorTargetUnsatisfied` when one does not fit. `planRecipeEvolution` is read-only: it merges each recipe
|
|
210
|
+
convention into the installed one additively, rechecks existing instances, and returns blockers
|
|
211
|
+
and a `plan_token` bound to everything it read. `applyRecipeEvolution` recomputes the plan,
|
|
212
|
+
refuses a different token, and writes each changed convention at exactly the version the plan
|
|
213
|
+
saw. The bundle has no cross-document transaction, so a failure part way reports the definitions
|
|
214
|
+
written and those still pending. A Connector has no additive merge: the plan replaces it whole,
|
|
215
|
+
after the conventions, and only when this recipe's stamp holds and its definition digest shows
|
|
216
|
+
nobody changed it; an edited, unstamped-and-different or foreign Connector is a blocker and is
|
|
217
|
+
never written, stamp included.
|
|
218
|
+
|
|
219
|
+
Every convention and Connector either path writes carries an `x_recipe` stamp in the same write:
|
|
220
|
+
the recipe's id, version, declared convention count (`of`), `recipeSourceDigest` of the recipe,
|
|
221
|
+
the `definitionDigest` of the document as written, and the `digest_rules` version. A recipe that
|
|
222
|
+
carries connectors adds `connectors`, its declared Connector count, which readers that predate it
|
|
223
|
+
ignore; a recipe without connectors writes and digests exactly as before. A plan's `changed` is
|
|
224
|
+
about content only; its `writes` also counts definitions whose stamp is stale or missing, so a
|
|
225
|
+
ready plan with `writes > 0` has work to apply. `installedRecipes(docs, known?)` reads each
|
|
226
|
+
recipe's state (`installed`, `partial`, `diverged`, `unknown`) from the stamps alone, without the
|
|
227
|
+
recipe source; give it the conventions and the Connectors (`installedConventionDocs` and
|
|
228
|
+
`installedConnectorDocs`).
|
|
229
|
+
|
|
230
|
+
Failures are typed subclasses of `RecipeLifecycleError`: `RecipeInputError` (the recipe cannot
|
|
231
|
+
target this bundle's edition), `ConnectorTargetUnsatisfied` (`recipe`, `connector`, `source`,
|
|
232
|
+
`kind`, `code`, `fields`), `RecipeAssetConflict` (`recipe`, `key`), `RecipeEditionChanged`,
|
|
233
|
+
`RecipeEvolutionPlanChanged` (`expected`, `current`), `RecipeEvolutionBlocked` (`blockers`),
|
|
234
|
+
`RecipeEvolutionInterrupted` (`reason`, `completed`, `pending`, `cause`) and
|
|
235
|
+
`RecipeEvolutionPostcondition` (`reason`, `completed`).
|
|
236
|
+
|
|
237
|
+
Declaration deprecation uses the same Kind/mutation owners, with explicit-registry transition
|
|
238
|
+
refusal and separate informational read usage. See [the grammar and admission limits](RECIPES.md#deprecating-declarations).
|
|
239
|
+
Adding lifecycle policy requires unchanged same-recipe ownership. Declaration removal is still
|
|
240
|
+
unsupported; existing scan, per-definition CAS and postcondition do not establish atomic non-use.
|
|
241
|
+
|
|
242
|
+
## Record overlay
|
|
243
|
+
|
|
244
|
+
`@superbee/core/record-overlay` is the write half of binding documents to external records. A
|
|
245
|
+
Source Feed policy names the fields a feed owns; the caller fetches and diffs its records and hands
|
|
246
|
+
the changes to the planner. It uses no Node globals or builtins, so it runs in Node, a browser or a
|
|
247
|
+
Worker over any storage backend.
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
import { MemoryBackend } from "@superbee/core/memory-backend";
|
|
251
|
+
import { buildKindRegistry } from "@superbee/core/kinds";
|
|
252
|
+
import { applyRecordOverlay, buildSourceIndex, planRecordOverlay, readOverlayPolicy } from "@superbee/core/record-overlay";
|
|
253
|
+
|
|
254
|
+
const bundle = { root: "memory://guide", backend: new MemoryBackend() };
|
|
255
|
+
const registry = buildKindRegistry(conventionDocs);
|
|
256
|
+
const guide = { bundle, registry, okfVersion: "0.2" as const };
|
|
257
|
+
const policy = readOverlayPolicy(sourceFeedDoc, { targetKind: registry.kinds.get("Event") });
|
|
258
|
+
const overlays = await buildSourceIndex(bundle, { type: policy.target_kind });
|
|
259
|
+
const plan = await planRecordOverlay({ content: guide, ops: guide, policy, changes, observedAt, overlays });
|
|
260
|
+
const receipt = await applyRecordOverlay(plan, { content: guide, ops: guide, policy, actor: "process:calendar-sync", overlays });
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
For each owned field the plan compares the last accepted feed value, the new feed value and the
|
|
264
|
+
document's value: a document that already says the new value is left alone, one that still says
|
|
265
|
+
the old value is written, and anything else is an `OVERLAY_DOC_CONFLICT` with a Signal, so an
|
|
266
|
+
editor's override is never overwritten. Each step carries the version it was planned against.
|
|
267
|
+
`applyRecordOverlay` writes every step through `mutateDocument` with a guard core builds itself and
|
|
268
|
+
runs on the fresh read: an owned-field write reaches documents of the policy's target Kind only,
|
|
269
|
+
may change only owned and derived fields, never the body, and needs the record's `sources`
|
|
270
|
+
identity; a Signal may be created only as a new, unclaimed document of the attention Kind under its
|
|
271
|
+
path, and updated only in a fixed set of fields plus one appended observation line. Signal writes
|
|
272
|
+
are bound to the provider of the policy the caller passes, never to a field of the plan, and an id
|
|
273
|
+
held by another provider's Signal is refused. A Signal's text must be the planner's rendering of
|
|
274
|
+
its own fields and the run (its observation line, links to the plan's feed and to documents, the
|
|
275
|
+
adoption hint): every plan the planner makes passes, and other text is a held `INVALID_INPUT` row,
|
|
276
|
+
so a mismatch is visible and planned again rather than dropped. Nothing of the
|
|
277
|
+
target Kind is created and nothing is deleted. A feed never owns its target's work state: the
|
|
278
|
+
progress storage fields and the target Kind's claim fields are refused like `status`. The
|
|
279
|
+
attention Kind (default `Signal`) must declare a claim owner and state field and the fields
|
|
280
|
+
`change_kind`, `observed_at`, `provider`, `external_id` and `diff_summary`.
|
|
281
|
+
|
|
282
|
+
Site rules are hooks with safe defaults: `textGate` (identity), `interval` (none, so a disappeared
|
|
283
|
+
record is always raised), `correlate`, `sourceProperties` (an upsert of the record's `sources`
|
|
284
|
+
entry in the same write, OKF v0.2 only), `assertCandidate` (site candidate checks after the core
|
|
285
|
+
guards) and `deriveFields` (fields such as `updated_at` written in the same version). Refusals are
|
|
286
|
+
receipt rows with a `REFUSAL_CODES` code; `RETRYABLE` and `HOLD_STORE` say which leave a record to be
|
|
287
|
+
planned again. A value core's write policy refuses (an OKF v0.2 field type, for example) is a held
|
|
288
|
+
`INVALID_INPUT` row, so one bad record never stops the rest of a run. A malformed plan (an unknown
|
|
289
|
+
step, a non-canonical target, written values that are not JSON data with finite numbers, prototype
|
|
290
|
+
keys, non-scalar or identity `sources` properties), actor or bundle edition throws `OverlayRefusal`
|
|
291
|
+
before any write, and so does a plan with owned-field steps whose policy names no declared target
|
|
292
|
+
Kind or whose owned or derived list names a field a feed can never own.
|
|
293
|
+
|
|
294
|
+
`SourceIndex` holds one head read for a whole run. It is bounded to that run: an edited or deleted
|
|
295
|
+
overlay is still caught at write time, but a second document given an identity during the run is
|
|
296
|
+
only refused as ambiguous on the next run.
|
|
297
|
+
|
|
298
|
+
## Connectors
|
|
299
|
+
|
|
300
|
+
`@superbee/core/connectors` parses Connector declarations: `type: Connector` documents under
|
|
301
|
+
`connectors/` that say what an integration is, independent of any one target Kind. A declaration
|
|
302
|
+
holds no authority and nothing in the subpath fetches, executes or receives. It uses no Node globals
|
|
303
|
+
or builtins.
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
import { bindSourceFeed, changeBetween, connectorPinDigest, findingsFor, mapRecord, parseConnectorDoc } from "@superbee/core/connectors";
|
|
307
|
+
|
|
308
|
+
const parsed = parseConnectorDoc(connectorDoc, { kinds: registry });
|
|
309
|
+
if (!parsed.ok) throw new Error(parsed.errors.map((e) => `${e.code} ${e.path}`).join("; "));
|
|
310
|
+
const pin = connectorPinDigest(parsed.connector);
|
|
311
|
+
const bound = bindSourceFeed(parsed.connector, sourceFeedDoc, registry, { okfVersion: "0.2" });
|
|
312
|
+
const mapped = mapRecord(bound.source, normalizedRecord, bound.kind);
|
|
313
|
+
if (mapped.kind === "mapped") {
|
|
314
|
+
const change = changeBetween(lastAcceptedAfter, mapped, { external_id: mapped.external_id, change_kind: "changed" });
|
|
315
|
+
}
|
|
316
|
+
const findings = findingsFor(mapped.diagnostics, { source: bound.source.name });
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
`auth.kind` is `none`, `token`, `github-app`, `oauth2` or `signed-webhook`. A credential's hosts
|
|
320
|
+
come only from core: `token` and `oauth2` name a row of `AUTH_PROVIDERS`, `github-app` is fixed,
|
|
321
|
+
and `credentialHosts` is the one reader. `domains` lists hosts for keyless fetches and may not
|
|
322
|
+
repeat a credential host. The sources live under `record_sources` (OKF v0.2 reserves `sources` for
|
|
323
|
+
provenance, and `CONNECTOR_DOCUMENT_KEYS` lists every top-level key a Connector adds, none of them
|
|
324
|
+
an OKF field); the parsed `Connector.sources` holds them. Each source names a catalog `profile`, a
|
|
325
|
+
`target_kind`, mode `overlay`, an identity template (`identity.resource`, an https URL whose
|
|
326
|
+
placeholders are feed parameters, and `identity.id` over record paths), `completeness`, and
|
|
327
|
+
`fields`: target field to a dotted record
|
|
328
|
+
path, or to `{ from, values, unmapped }`. Mapping keys are the most a Source Feed may own; never-owned
|
|
329
|
+
fields, managed metadata, work state, the body and list-typed fields are refused. Actions and events
|
|
330
|
+
are declared shapes only. Every nested block is closed by `CONNECTOR_KEYS`; `parseConnectorDoc`
|
|
331
|
+
returns every refusal with a typed `code` and a JSON pointer `path`. A token shape or URL userinfo
|
|
332
|
+
anywhere in a frontmatter string, key or the body, or a credential key name outside the data-name
|
|
333
|
+
maps, refuses the document alone, naming a path and never the value; so does a YAML alias, which
|
|
334
|
+
keeps every reader bounded by the document's size. The secret rule is a tripwire; the pin, the
|
|
335
|
+
credential store and the fetcher are the boundaries.
|
|
336
|
+
|
|
337
|
+
`connectorPinDigest` covers every key `CONNECTOR_KEYS` accepts, with defaults filled and order-free
|
|
338
|
+
lists sorted, plus the credential hosts core resolves the auth kind to, so a host joining an
|
|
339
|
+
`AUTH_PROVIDERS` row changes the pin. It is blind to the title, description, version, body and
|
|
340
|
+
unknown top-level keys. `CONNECTOR_PIN_RULES` changes whenever a default or sort rule does.
|
|
341
|
+
|
|
342
|
+
`bindSourceFeed` binds a Source Feed (`connector`, `source`, `url`, `owned_fields`) to one source:
|
|
343
|
+
the `url` must equal the identity template rendered from parameters of `[A-Za-z0-9._~-]+` byte for
|
|
344
|
+
byte, `owned_fields` must be mapping keys, and the source's `target_kind`, `completeness` and
|
|
345
|
+
`profile` win. `okfVersion` is the content bundle's edition (absent means v0.1); a feed owning a
|
|
346
|
+
Kind-declared list field binds only on v0.2. Each refusal is an `OverlayRefusal` with
|
|
347
|
+
`POLICY_INVALID`, the feed and the key.
|
|
348
|
+
`mapRecord` never lets a mapping gap erase a stored value: an absent path is unobserved, an
|
|
349
|
+
unmapped native is kept (`unmapped: keep`, the default) or refuses the record while it still counts
|
|
350
|
+
as present (`refuse`), and only an empty value (`null` or blank text) clears an optional field; on a
|
|
351
|
+
required field or `title` it is kept with a finding. An id template separates its placeholders with
|
|
352
|
+
literal text, and a record whose value holds that text, is blank or makes an id past 2048
|
|
353
|
+
characters is refused as `IDENTITY_MISSING` or `IDENTITY_INVALID`, so two records never share one id. `changeBetween` takes the last accepted `after`, carries
|
|
354
|
+
unobserved fields and leaves them out of `changed_fields`. `findingsFor` groups diagnostics per
|
|
355
|
+
source, field, code and value (a list, mapping or empty value by its shape) for `planFindings`, so
|
|
356
|
+
one problem across many records raises one Signal.
|
|
357
|
+
|
|
358
|
+
A recipe carries Connectors through its manifest's `connectors:` list (`parseRecipeFiles` reads
|
|
359
|
+
each with `parseConnectorDoc`, checking the sources that target the recipe's own Kinds, and refuses
|
|
360
|
+
a malformed one as `RECIPE_CONNECTOR_INVALID`); `@superbee/core/recipe-lifecycle` installs, stamps
|
|
361
|
+
and evolves them as definitions.
|
|
362
|
+
|
|
363
|
+
The public [superbee-plugins recipes](https://github.com/Holaxis-ai/superbee-plugins/tree/main/recipes)
|
|
364
|
+
own the `Source Feed`, `Signal` and Work Item definitions used by the GitHub integration. The generic
|
|
365
|
+
engine and synthetic conformance fixtures remain in this package.
|
|
366
|
+
|
|
367
|
+
## Source engine
|
|
368
|
+
|
|
369
|
+
`@superbee/core/source-engine` is the portable half of the integration source engine: the one
|
|
370
|
+
loop that observes a bound Source Feed for an author's test, a local run or a managed runner. It
|
|
371
|
+
uses no Node globals or builtins and never calls `fetch`; the caller supplies the network as a
|
|
372
|
+
`Transport`.
|
|
373
|
+
|
|
374
|
+
```ts
|
|
375
|
+
import { MemoryMachineState, fetchIdentity, observeSource } from "@superbee/core/source-engine";
|
|
376
|
+
|
|
377
|
+
const receipt = await observeSource({
|
|
378
|
+
connector, bound, profile, spec, transport, state: new MemoryMachineState(),
|
|
379
|
+
identity: fetchIdentity("Product/1.0 (+https://example.org/about)")!,
|
|
380
|
+
mode: "incremental", signal: AbortSignal.timeout(60_000),
|
|
381
|
+
commit: async (page) => {
|
|
382
|
+
const plan = await planRecordOverlay({ content, ops, policy: bound.policy, changes: page.changes, observedAt: page.observed_at, records: page.records });
|
|
383
|
+
await applyRecordOverlay(plan, { content, ops, policy: bound.policy, actor: "process:calendar-sync" });
|
|
384
|
+
},
|
|
385
|
+
});
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
A `SourceProfile` (`id`, `version`, `kind`, `validateSpec`, `hostsFor`, `observe`) turns a bound
|
|
389
|
+
feed, a cursor and a transport into pages of normalized records: `{ records, nextCursor, more,
|
|
390
|
+
complete }`, all JSON data. It never opens a socket, never sees a credential and never writes. A
|
|
391
|
+
`Transport` answers `{ url, method, headers, body, maxBytes, credentialHost, signal }`; it never
|
|
392
|
+
follows a redirect and returns `{ ok: false, refused: "too_large" }` rather than a truncated body.
|
|
393
|
+
A credential-injecting transport adds its credential only when the engine's `credentialHost` is
|
|
394
|
+
true and its own exact match agrees: https, the default port, a host exactly one of the auth
|
|
395
|
+
kind's credential hosts.
|
|
396
|
+
|
|
397
|
+
The profile sees the engine's guarded transport, which checks every URL and every redirect hop:
|
|
398
|
+
https only, the lexical address rule (`refusedAddress`: no IP literal, local name, port or
|
|
399
|
+
userinfo), and a host within the profile's `hostsFor` and the connector's credential hosts plus
|
|
400
|
+
keyless `domains`. A `hostsFor` naming any other host refuses the run before a request. A redirect
|
|
401
|
+
never changes host when either side is a credential host. Keyless hosts are read through robots.txt
|
|
402
|
+
(RFC 9309, the declared identity's product token, the first 512 KiB) first, and a bot-mitigation
|
|
403
|
+
page from one is refused and never retried. Only `GET` is sent unless `rules.post` admits a POST
|
|
404
|
+
toward a credential host; a keyless host is GET only. A GET with a body, a POST redirect, and the
|
|
405
|
+
headers `authorization`, `cookie`, `proxy-authorization`, `host`, `user-agent` and the method
|
|
406
|
+
overrides are always refused; beyond `accept` a header needs `rules.header`, and the validators
|
|
407
|
+
`if-none-match` and `if-modified-since` go out in incremental mode only. The budget counts every
|
|
408
|
+
request, robots.txt and hops included. Each request ends after `requestTimeoutMs` (default 20
|
|
409
|
+
seconds). The deadline is the caller's `signal`; without one the run ends after `deadlineMs`
|
|
410
|
+
(default 5 minutes). The deadline bounds the profile's page too: a page still pending is discarded
|
|
411
|
+
and the run ends `deferred`. A commit or state write in progress is never abandoned, so a hung one
|
|
412
|
+
holds the run. Statuses map to outcomes: 401 and 403 `credential_refused` (no retry), 404 and
|
|
413
|
+
410 `not_found`, 429 `deferred` with `retry_after`, others `provider_unavailable` (503 included,
|
|
414
|
+
with a retry); `statuses` moves a status between the sets. Any refusal or error status stops the
|
|
415
|
+
run, even when the profile catches the error, and the guarded transport is closed when the run
|
|
416
|
+
ends.
|
|
417
|
+
|
|
418
|
+
A profile runs in the engine's realm. The guard bounds what a declaration and a provider's data can
|
|
419
|
+
reach; profile code itself is bounded by the repository's static egress check
|
|
420
|
+
(`scripts/source-egress-check.mjs`) and by review, until profiles run in an isolate. The check
|
|
421
|
+
refuses globals outside a short list, imports outside an allowlist or the scanned roots, writes to
|
|
422
|
+
anything reached from a global, the prototype, property-definition and descriptor names however
|
|
423
|
+
they are read, `this` outside a class body and CommonJS; a key computed at run time is beyond any
|
|
424
|
+
static check.
|
|
425
|
+
|
|
426
|
+
Each page is mapped with `mapRecord`, diffed against the stored records into `RecordChange` rows
|
|
427
|
+
and handed to `commit`; the cursor and the records are stored only after `commit` resolves. A page
|
|
428
|
+
that promises more with no cursor, or with a cursor the run already asked for, fails the run as
|
|
429
|
+
`cursor_stuck`. `commit` may decline to write changes: a `retry` change (a lost compare-and-swap, a
|
|
430
|
+
claimed Signal) stays pending in the machine state and is planned again at the start of every run,
|
|
431
|
+
before anything is fetched, until it is written, refused for good, or superseded by a newer
|
|
432
|
+
observation of the record (its listing, or its absence from a complete pass); a replay keeps the
|
|
433
|
+
record's missing count, and a record that disappeared is not replayed. A replay whose commit throws
|
|
434
|
+
stays pending with a finding while the run reads on, and is dropped with a finding after
|
|
435
|
+
`MAX_REPLAY_FAILURES` (5). A page that supersedes pending changes stores their removal before its
|
|
436
|
+
commit, so a crash after the commit never replays an older change. A `hold` change is not pending,
|
|
437
|
+
and its record is planned again when it is next observed. Either way the cursor moves on, so one
|
|
438
|
+
record never stops a feed. An incremental
|
|
439
|
+
run continues the stored cursor (`resumed` when the last run left its pass open); a complete run
|
|
440
|
+
lists from the start, sends no validator, and stops on a not-modified answer. A pass is a complete
|
|
441
|
+
observation only when it ran in complete mode to its last page within the run, the profile claimed
|
|
442
|
+
it complete and the source is declared `complete`; only then is a listed record's absence counted,
|
|
443
|
+
and a record is `disappeared` after the feed's `missing_threshold` (default 2) of them. A cursor
|
|
444
|
+
from another profile id or version is never handed back. The receipt names the outcome, every
|
|
445
|
+
request, the pass, the stored cursor, the held and pending counts, the mapping diagnostics and the
|
|
446
|
+
findings for `planFindings`. `MachineState` is the port for the stored state, written with a
|
|
447
|
+
compare-and-swap; `MemoryMachineState` keeps it in memory.
|
|
448
|
+
|
|
449
|
+
## Source harness
|
|
450
|
+
|
|
451
|
+
`@superbee/core/source-harness` is how a profile and a transport prove the engine's rules offline.
|
|
452
|
+
It returns rows and problem lists rather than asserting, so any test runner can use it, and it uses
|
|
453
|
+
no Node globals or builtins.
|
|
454
|
+
|
|
455
|
+
```ts
|
|
456
|
+
import { peerTransport, runSourceFixture, runTransportConformance } from "@superbee/core/source-harness";
|
|
457
|
+
|
|
458
|
+
const { problems, missing } = await runSourceFixture(fixture, scenarios);
|
|
459
|
+
const rows = await runTransportConformance({ name: "mine", create: (peer, credential) => myTransport(peer, credential) });
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
`runSourceScenario` replays a scenario's scripted provider answers (`exchanges`, each used once,
|
|
463
|
+
matched on method and URL; a `hang` answer aborts the run's deadline) through `observeSource`,
|
|
464
|
+
writing each committed page through record-overlay over a MemoryBackend bundle built from the
|
|
465
|
+
fixture's documents, with editor edits between runs. `checkScenario` holds the result to the rule
|
|
466
|
+
of its required scenario (`REQUIRED_SCENARIOS`: provider errors, an incomplete page, cursor resume,
|
|
467
|
+
a hostile cursor refused by the guard, disappearance only from complete observations, owned-field conflicts, unmapped
|
|
468
|
+
and empty values, a missing identity, a cross-host redirect and an undeclared host), and fails any
|
|
469
|
+
request to an undeclared host or one the fixture did not script; a name may add a variant
|
|
470
|
+
(`cursor.resume.page-number`). `missingScenarios` lists what a fixture lacks.
|
|
471
|
+
|
|
472
|
+
`runTransportConformance` is the suite every transport passes against a `ScriptedPeer`, the
|
|
473
|
+
network that records exactly what arrived: the credential reaches its host under its own header
|
|
474
|
+
name (a custom `accessToken` included) and no other host, lookalikes included (a longer or
|
|
475
|
+
shorter name, a trailing dot, userinfo, another port), nor its own host when the engine's
|
|
476
|
+
`credentialHost` is false; a redirect is returned unfollowed, a body
|
|
477
|
+
over the bound is refused while one at the bound is returned whole, and behind the engine a
|
|
478
|
+
redirect to another host or a private address reaches nothing. `peerTransport` is a memory
|
|
479
|
+
transport that passes it.
|
|
480
|
+
|
|
481
|
+
## Bundle health
|
|
482
|
+
|
|
483
|
+
`@superbee/core/bundle-health` computes the health report behind `superbee status` from a core
|
|
484
|
+
`Bundle` on any backend: malformed documents, Kind lint and conformance debt, registry warnings,
|
|
485
|
+
unresolved links, orphans, freshness and invalid timestamps, trust tiers, the two graph lints, the
|
|
486
|
+
View surface, cross-bundle links, the OKF v0.1 upgrade finding and legacy naming. It uses no Node
|
|
487
|
+
globals or builtins, so it runs in Node, a browser or a Worker. The caller presents the report;
|
|
488
|
+
the CLI adds row caps, help text and its home and sync blocks.
|
|
489
|
+
|
|
490
|
+
```ts
|
|
491
|
+
import { MemoryBackend } from "@superbee/core/memory-backend";
|
|
492
|
+
import { bundleHealth } from "@superbee/core/bundle-health";
|
|
493
|
+
|
|
494
|
+
const bundle = { root: "memory://guide", backend: new MemoryBackend() };
|
|
495
|
+
const report = await bundleHealth(bundle, { now: new Date() });
|
|
496
|
+
const gate = { malformed: report.malformed.length, kind_warnings: report.kindLint.length, unresolved_links: report.unresolvedLinks.length };
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
The report reads only: one Kind registry load, one lenient query, two prefix-scoped blob listings
|
|
500
|
+
and one root-index read, with every finding derived in memory. A document whose frontmatter does
|
|
501
|
+
not parse is a `malformed` row (the root index first), never a thrown error. Top-level keys are
|
|
502
|
+
camelCase; rows keep snake_case keys (`age_ms`, `bundle_id`, `incompatible_values`) and arrive
|
|
503
|
+
whole, in report order, for the caller to cap. Sections whose presence depends on the bundle are
|
|
504
|
+
null when absent: `conformanceDebt` without any declared Kind, `trust` outside a non-empty v0.2
|
|
505
|
+
bundle, `crossBundle` without `docs/dependencies` or any `okf:` link, `okfUpgrade` outside a v0.1
|
|
506
|
+
bundle with a workflow-status collision. `crossLinkTypeViolations` is always an array, because a
|
|
507
|
+
local edge under a qualified declaration needs no dependencies. The stored values in
|
|
508
|
+
`invalidStaleAfter`, `invalidTimestamps` and `invalidLegacyTimestamps` are made JSON-safe;
|
|
509
|
+
`missingExpectedLinks[].status` is the raw stored value.
|
|
510
|
+
|
|
511
|
+
Nothing outside the bundle is read unless the caller binds it. `bindings` is a map of bound copies
|
|
512
|
+
by alias, or a hook core calls exactly once, after every read and with its own parse of
|
|
513
|
+
`docs/dependencies` (a self alias already dropped as `DEP_SELF`, using `selfBundleId` when
|
|
514
|
+
`docs/bundle` declares no id). Whatever the hook throws propagates unchanged. Each binding's
|
|
515
|
+
verdict comes from core's resolver, and `dependencies[].bound` is the binding's `label`.
|
|
516
|
+
|
|
517
|
+
`bundleKindFindings(documents, okfVersion, { actor, producer })` answers a different question:
|
|
518
|
+
which Kind problems a strict host write would refuse, preparing each document as its first edit,
|
|
519
|
+
so a timestamp or actor the write supplies is no finding. The CLI's publish preview runs it; the
|
|
520
|
+
report's `kindLint` stays advisory. Both use core's one validator, `validateAgainstKind`.
|
|
521
|
+
|
|
522
|
+
## Search
|
|
523
|
+
|
|
524
|
+
`@superbee/core/search` is the one `documents.search.v1` engine: the request and result contract,
|
|
525
|
+
query normalization, ranking (`portal-webmcp-search/v1`), the any-term fallback, facets, cursors
|
|
526
|
+
and page budgets. A backend supplies what it searches through `SearchSource`: one `evidence()`
|
|
527
|
+
call per request and `text()` for the page's excerpts. `memorySearchSource` indexes a complete
|
|
528
|
+
corpus in memory. `searchSourceFromBackend(backend, options?)` reads a complete local
|
|
529
|
+
backend in bounded batches, applies the same memory-source bounds, and derives the corpus key
|
|
530
|
+
from the versions actually read. Validate the request before calling it and pass an opaque
|
|
531
|
+
bundle identity as the search scope. Failed reads refuse the whole corpus. The subpath uses
|
|
532
|
+
no Node globals or builtins.
|
|
533
|
+
|
|
534
|
+
```ts
|
|
535
|
+
import { memorySearchSource, searchDocuments } from "@superbee/core/search";
|
|
536
|
+
|
|
537
|
+
const source = memorySearchSource(documents, { key: headsDigest });
|
|
538
|
+
const result = await searchDocuments(source, { query: "lock keeper", limit: 10 }, { scope: bundleId });
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
`key` identifies the corpus; a cursor is refused for another key, scope, query, match or filters
|
|
542
|
+
(`type`, `tag`, `prefix`). A request outside the contract throws `SearchInputError`; a corpus over a
|
|
543
|
+
bound throws `SearchLimitError`; a request whose terms hold more than `maxEvidenceInstances` capped
|
|
544
|
+
instances (a `searchDocuments` option, at most `SEARCH_LIMITS.maxEvidenceInstances`) throws
|
|
545
|
+
`SearchBroadQueryError`, a `SearchLimitError` whose message asks for fewer or more specific terms.
|
|
546
|
+
A source must hold one document per id.
|
|
547
|
+
A document over 65,536 body terms is left out and counted: the result's `corpus` reports
|
|
548
|
+
`documents`, `indexed`, `skipped` and `reindexPending`. `searchRowText` is what one document
|
|
549
|
+
contributes to any index, and `textVersion()` names the algorithm, `SEARCH_TEXT_REVISION` and the
|
|
550
|
+
runtime's normalizer fingerprint, so a stored index can tell when its rows need re-deriving.
|
|
551
|
+
|
|
552
|
+
## Hosted document backend
|
|
553
|
+
|
|
554
|
+
`createHostedDocumentBackend` from `@superbee/core/hosted-transport` composes the existing
|
|
555
|
+
hosted read adapter and whole-document transport into a finite document `StorageBackend`. Supply
|
|
556
|
+
`reader`, `carrier`, `scope`, `intents` and a positive `maximumDocuments`. The reader and carrier
|
|
557
|
+
must address the same trusted scope: tenant, bundle, registration, checkout binding, workspace
|
|
558
|
+
(`null` when unnamed), and OKF edition. Scope also pins `mode: "write"`,
|
|
559
|
+
`bodyPolicy: "protected"` and `tombstonePolicy: "refuse"`. Credentials, authorization and effect
|
|
560
|
+
fences belong to the host carrier and shared write entry.
|
|
561
|
+
|
|
562
|
+
Reads and root metadata use the read adapter. `list(prefix)` admits the entire validated paged
|
|
563
|
+
heads inventory before filtering, and refuses overflow. `exists` converts only ENOENT to false.
|
|
564
|
+
Every write requires an explicit `expectedVersion` (`null` for create, a content version for
|
|
565
|
+
replace) and a stable lowercase UUIDv4 `requestId`. The returned version is the host's version.
|
|
566
|
+
A valid OKF `actor` is advisory input only; host-owned attribution and normal body protection
|
|
567
|
+
remain in effect. Defined `agent`, `bodyReplace` and other unsupported options refuse. History,
|
|
568
|
+
deletion, recreation, blobs and reserved-file writes refuse. This adapter exposes neither general
|
|
569
|
+
wire capabilities nor an inventory guard.
|
|
570
|
+
|
|
571
|
+
The injected `HostedDocumentIntentPort` must atomically prepare-or-read and exclusively claim a
|
|
572
|
+
**new** descriptor, durably recording `in_flight` and attempt 1 before returning `fresh: true`
|
|
573
|
+
to exactly one caller. Every existing descriptor returns `fresh: false`, including an unattempted
|
|
574
|
+
pending record. Core validates the immutable scope/input digest and uses the original content and
|
|
575
|
+
creation instant on lookup. A lost claim acknowledgement prevents submission. Every reopened
|
|
576
|
+
record is lookup-only: absent, pending, expired or unavailable receipts never authorize another
|
|
577
|
+
send or a replacement identity. The port is a trusted persistence contract; Core supplies no
|
|
578
|
+
journal or storage implementation for it.
|
|
579
|
+
|
|
580
|
+
`settle` must durably compare the original digest and retain its descriptor and exact outcome;
|
|
581
|
+
a definitive outcome is immutable, and contradictory settlement must refuse.
|
|
582
|
+
A refusal marked `executed: "unknown"` is unresolved attempt evidence, including in older stored
|
|
583
|
+
records. It remains eligible for original-identity lookup and replacement by the recorded truth;
|
|
584
|
+
it must never be cached as a definitive refusal. Keep both until the importing consumer acknowledges
|
|
585
|
+
acceptance. Success is returned only after settlement completes. A settlement failure throws
|
|
586
|
+
`HostedDocumentBackendError` with `code: "outcome_unknown"`, the original `requestId`, and the
|
|
587
|
+
retained descriptor (including a known remote outcome). Unknown receipt results use the same error
|
|
588
|
+
classification. A `HostedDocumentVersionConflict` extends `VersionConflict` and retains the original
|
|
589
|
+
descriptor and any tombstone receipt. Deterministic refusals retain their existing transport code.
|
|
590
|
+
No matching-content read is used to manufacture success.
|
|
591
|
+
|
|
592
|
+
Callers above `StorageBackend` must forward the stable identity and immutable mutation input.
|
|
593
|
+
`mutateDocument` forwards `requestId` in `create-only` mode. Its other mutation modes reject that
|
|
594
|
+
option; identified replacements remain explicit conditional backend writes.
|
|
595
|
+
This backend is browser/Worker safe and uses the same carrier and response grammar as hosted's
|
|
596
|
+
other Core consumers.
|
|
597
|
+
|
|
598
|
+
## Hosted client
|
|
599
|
+
|
|
600
|
+
`@superbee/core/hosted-client` is the headless client of a hosted Superbee's sync routes
|
|
601
|
+
(`/sync/v1`). It is a supported public API under core's normal versioning; the `superbee` CLI uses
|
|
602
|
+
it. It runs wherever `fetch` does (Node, a browser or a Worker) and reads identity (`whoami`),
|
|
603
|
+
the bundle list (`bundles`, with `readBundleListing`), documents (`reader`), document history
|
|
604
|
+
(`history`) and operations run by id (`listOperations`, `runOperation`). `within(slug)` returns
|
|
605
|
+
the same client naming every bundle-scoped request's bundle as `<slug>/<bundle-id>`.
|
|
606
|
+
|
|
607
|
+
```ts
|
|
608
|
+
import { bearerCredentials, createHostedClient } from "@superbee/core/hosted-client";
|
|
609
|
+
|
|
610
|
+
const client = createHostedClient({
|
|
611
|
+
target: { origin: "https://mcp.getsuperbee.com", syncPrefix: "/sync/v1" },
|
|
612
|
+
credentials: bearerCredentials(() => getToken()),
|
|
613
|
+
});
|
|
614
|
+
const identity = await client.whoami();
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
The target is the origin and the sync prefix: `/sync/v1`, or `/agents/<id>/sync/v1` for an agent
|
|
618
|
+
connection. `syncRoutePrefix({ origin, audience })` derives the prefix from a protected-resource
|
|
619
|
+
audience and returns null when the audience has none. `credentials` is the transport carrier's
|
|
620
|
+
own credential provider: an async function returning the headers for one request. It is called
|
|
621
|
+
before every request, so it may renew a short-lived token. `bearerCredentials` builds one from a
|
|
622
|
+
token getter. The client never logs credentials, and its errors carry the route and code, never a
|
|
623
|
+
token.
|
|
624
|
+
|
|
625
|
+
Failures are typed and carry no wording for any particular surface:
|
|
626
|
+
|
|
627
|
+
- `HostedCarrierError`: transport. `denied` means no credential (nothing was sent) or the request
|
|
628
|
+
was refused before sending; `unavailable` means the answer is unknown. `isAnswerTooLarge`
|
|
629
|
+
distinguishes an answer larger than its bound.
|
|
630
|
+
- `MalformedAnswer` (`code` `MALFORMED_ANSWER`, `route`): the host answered something the
|
|
631
|
+
contract does not admit. Retrying gets the same answer.
|
|
632
|
+
- `RemoteError` (`code`, `status`): the host refused the request, with its own code.
|
|
633
|
+
- `HostedRouteNotServed` (`code` `ROUTE_NOT_SERVED`, status 404, `route`): a `RemoteError` for a
|
|
634
|
+
host from before the route.
|
|
635
|
+
|
|
636
|
+
Operation and history refusals the host states in a `200` (and `bundle_not_found`,
|
|
637
|
+
`unknown_operation` and `invalid_input`) come back as `{ ok: false, refusal }`, not as errors.
|
|
638
|
+
|
|
639
|
+
### Writing a document
|
|
640
|
+
|
|
641
|
+
`write` sends one whole serialized document (frontmatter and body, as a file holds it) to a hosted
|
|
642
|
+
bundle: a create of an absent id, or a replace at exactly the version the caller read.
|
|
643
|
+
|
|
644
|
+
```ts
|
|
645
|
+
const created = await client.write("notes", { id: "notes/today", document: text, expectAbsent: true });
|
|
646
|
+
const replaced = await client.write("notes", { id: "notes/today", document: edited, expectedVersion: version });
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
It returns one of:
|
|
650
|
+
|
|
651
|
+
- `{ status: "committed", version }`: the host stored it; `version` is the new head.
|
|
652
|
+
- `{ status: "conflict", reason: "version_conflict", currentVersion }`: the document is not at the
|
|
653
|
+
expected version (for a create: it exists). `currentVersion` is the version the host serves, or
|
|
654
|
+
null when it serves none. Nothing was written.
|
|
655
|
+
- `{ status: "conflict", reason: "concurrent_change" }`: another write to the bundle overlapped
|
|
656
|
+
this one past the host's own retries. Nothing was written.
|
|
657
|
+
- `{ status: "refused", code, message }`: the host refused this write (`validation_failed`,
|
|
658
|
+
`invalid_input`, `AUTH_REQUIRED`, `PERMISSION_DENIED`, a sync quota's `REQUEST_CAPACITY_*`, or
|
|
659
|
+
`read_only` when the bundle serves no writes). Nothing was written.
|
|
660
|
+
- `{ status: "unknown", requestId }`: the host may or may not hold the write.
|
|
661
|
+
|
|
662
|
+
Every result names its `requestId`. A conflict is final for that identity: the client never
|
|
663
|
+
resends it, under that identity or a new one. Read the document and decide; another attempt is a
|
|
664
|
+
new `write`.
|
|
665
|
+
|
|
666
|
+
A write needs the bundle's capabilities (whether it serves writes, its OKF edition, the write
|
|
667
|
+
bound and the retention window). The client reads them once per bundle and workspace and holds the
|
|
668
|
+
answer for `capabilitiesTtlMs` (default 60,000 ms), shared by the client and its `within` clients
|
|
669
|
+
and never by `reader()`. So a write costs one round trip while its bundle's capabilities are fresh,
|
|
670
|
+
and two when they are read first; concurrent writes to one bundle share one read. A write refused
|
|
671
|
+
`read_only`, `invalid_input`, `validation_failed` or `PERMISSION_DENIED` (which is how a
|
|
672
|
+
`bundle_not_found` write answer arrives), or whose result is `unknown`, drops the held answer, and
|
|
673
|
+
a capabilities read that fails is never held, so the next write reads them again. Set
|
|
674
|
+
`capabilitiesTtlMs: 0` to read them before every write. The retention window that decides a
|
|
675
|
+
resend after an `absent` lookup (below) is never taken from a held answer: it is read again then.
|
|
676
|
+
|
|
677
|
+
Each write has one request identity and carries the client's binding, a digest of a random id the
|
|
678
|
+
client mints once (its `within` clients share it). When an answer is lost (a transport failure or
|
|
679
|
+
a deadline after sending), the client looks the identity up on the host and reports what the host
|
|
680
|
+
recorded. The host's outcome route tells a request it never received ("absent") from one still in
|
|
681
|
+
progress ("pending"), and the shared transport keeps the two apart. On either answer `write`
|
|
682
|
+
sends the same identity once more, which the host applies at most once; after "pending", an
|
|
683
|
+
earlier delivery is known to be held. Absence counts as
|
|
684
|
+
evidence only within the retention window the host serves in its capabilities
|
|
685
|
+
(`operationsRetentionMs`, less an hour's margin); past it, the result stays `unknown` and nothing
|
|
686
|
+
is resent. The client measures an intent's age by its own clock against the host's window, so that
|
|
687
|
+
margin holds only while the client's clock is within an hour of the host's. A resend refused before
|
|
688
|
+
it reaches the host's execution (no credential, access withdrawn, a spent quota) leaves the result
|
|
689
|
+
`unknown` too, since the first delivery may still commit; after a "pending" lookup, so does any
|
|
690
|
+
refusal before execution, a gateway's `400` included. The result's `refusal` (`{ code, message }`)
|
|
691
|
+
names that refusal, so an app can say why the write is waiting (for example, access withdrawn)
|
|
692
|
+
while it stays held; only the call that met the refusal reports it. Absence alone never settles
|
|
693
|
+
such a write: a delayed first delivery is authorized when it arrives, so it can still commit once
|
|
694
|
+
access returns. When the lookups themselves fail, or the caller's `signal` aborts after sending, the
|
|
695
|
+
result is `unknown`.
|
|
696
|
+
|
|
697
|
+
An `unknown` write can be resolved later in two ways:
|
|
698
|
+
|
|
699
|
+
- `lookupWrite(requestId)` makes one outcome lookup and never sends the write. When the host holds
|
|
700
|
+
no outcome yet (absent or pending), the answer stays `unknown`.
|
|
701
|
+
- `settleWrite(requestId)` looks the identity up and, when the host never recorded it within its
|
|
702
|
+
retention window or holds it pending, sends the same identity once more, as `write` would have.
|
|
703
|
+
|
|
704
|
+
Both answer `{ status: "unrecorded", requestId }` when the intent store holds no write under that
|
|
705
|
+
identity: it was never recorded there, or it already settled and left the store. Both return
|
|
706
|
+
`unknown` when the bundle's capabilities cannot be read, because the write may already have left.
|
|
707
|
+
`settleWrite` only looks up, sending nothing, when the host serves the bundle no writes. A
|
|
708
|
+
`signal` already aborted when `write`, `lookupWrite` or `settleWrite` is called is thrown as its
|
|
709
|
+
reason before any request; an abort during delivery leaves the write `unknown` and held.
|
|
710
|
+
`write` instead throws that failure, because it has sent nothing yet.
|
|
711
|
+
|
|
712
|
+
Until it settles, a write is held in an intent store: its operation, bundle (and workspace),
|
|
713
|
+
document id, document and digest, expected version, request identity and binding, which is
|
|
714
|
+
everything needed to rebuild the lookup exactly. The default store is in memory and lives as long
|
|
715
|
+
as the client. Pass `intentStore` (`put`, `get`, `delete`, and optionally `list`) to keep intents
|
|
716
|
+
durable across restarts (`openHostedClientJournal`, below, is one for Node); lookups use the
|
|
717
|
+
binding recorded with each intent. A service that
|
|
718
|
+
restarts with a durable store calls `heldWrites()` to find the writes that never settled
|
|
719
|
+
(including any recorded but never sent), then settles each with `settleWrite`. `heldWrites()`
|
|
720
|
+
throws `HostedIntentsNotListable` when the store has no `list`. A settled write leaves the store.
|
|
721
|
+
|
|
722
|
+
`write(..., { requestId })` with the identity of a held write resumes that write and applies
|
|
723
|
+
nothing new. A different write under a held identity, including the same write named in another
|
|
724
|
+
workspace, is refused before sending. The host refuses an identity it recorded for another
|
|
725
|
+
document (`invalid_input`). Calls on one client that name the same identity run one after another.
|
|
726
|
+
|
|
727
|
+
`write` throws only before anything is sent: a malformed input (`TypeError`), the typed error of
|
|
728
|
+
its capabilities read, or a failure of the intent store to record the write. Document deletion is
|
|
729
|
+
not part of `write`.
|
|
730
|
+
|
|
731
|
+
### Writing a blob
|
|
732
|
+
|
|
733
|
+
`writeBlob` and `deleteBlob` write one ordinary file (a blob: an image, a diagram, any bytes) to a
|
|
734
|
+
hosted bundle. A create names an absent key; a replace and a delete name the head they expect,
|
|
735
|
+
its version **and** content type (a `BlobPin`), so a type-only change is a replace too.
|
|
736
|
+
|
|
737
|
+
```ts
|
|
738
|
+
const svg = new TextEncoder().encode("<svg xmlns='http://www.w3.org/2000/svg'/>");
|
|
739
|
+
const created = await client.writeBlob("notes", { key: "diagrams/flow.svg", bytes: svg, contentType: "image/svg+xml", expectAbsent: true });
|
|
740
|
+
if (created.status !== "committed") throw new Error(created.status);
|
|
741
|
+
let pin = created.head!; // { key, version, contentType }
|
|
742
|
+
|
|
743
|
+
const replaced = await client.writeBlob("notes", { key: pin.key, bytes: edited, contentType: "image/svg+xml", expected: pin });
|
|
744
|
+
if (replaced.status === "committed") pin = replaced.head!;
|
|
745
|
+
|
|
746
|
+
const read = await client.readBlob("notes", pin.key, { pin }); // bytes verified, or null if no longer at the pin
|
|
747
|
+
const deleted = await client.deleteBlob("notes", { key: pin.key, expected: pin });
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
Each call returns one of:
|
|
751
|
+
|
|
752
|
+
- `{ status: "committed", head, changed? }`: applied. `head` is the new head (null after a
|
|
753
|
+
delete); `changed` is false for a replace the head already matched.
|
|
754
|
+
- `{ status: "conflict", reason: "version_conflict", actual }`: the head is not at the expected pin
|
|
755
|
+
(for a create: the key exists). `actual` is the head now, or null when there is none (a delete of
|
|
756
|
+
an absent key). Final for that identity.
|
|
757
|
+
- `{ status: "conflict", reason: "concurrent_change", message }`: another write overlapped this one
|
|
758
|
+
past the host's own retries. Nothing was applied.
|
|
759
|
+
- `{ status: "refused", code, message }`: the host's own refusal, or one the client names. The
|
|
760
|
+
host's codes are those it records under the identity (`backend_unavailable`, ...) and those it
|
|
761
|
+
answers without writing and never records (`insufficient_scope` for a grant without blob writes,
|
|
762
|
+
`bundle_not_found`, `request_capacity` in a `200` or a `429`, and `path_collision` for a create
|
|
763
|
+
whose key, or one of its folders, differs from an existing document or blob path only by letter
|
|
764
|
+
case or Unicode form under `foldPath`, the per-folder rule portable export applies; a create
|
|
765
|
+
only, and it never resends: rename the key). A refusal of the second kind
|
|
766
|
+
proves only that this attempt did not run, so it is final only when every earlier attempt of the
|
|
767
|
+
write is proven not applied, which is always so for a write the client sends; a write whose
|
|
768
|
+
earlier attempt was unclear is never sent again, only looked up, and stays `unknown`.
|
|
769
|
+
`insufficient_scope`, `bundle_not_found` and `invalid_input` also drop the held capabilities
|
|
770
|
+
answer, so the next write reads them again. The client's own codes are `invalid_input` (an
|
|
771
|
+
unsafe key, a key under one of Superbee's own record folders or a reserved file, an invalid content type, a body over the host's `blobWriteBytes` or 16 MiB, or a held
|
|
772
|
+
identity reused for another write), `scope_mismatch` (a held write, or a reused `requestId`, recorded
|
|
773
|
+
for another origin or sync prefix) or `unsettled_write` (an earlier write of the same key in the bundle is still
|
|
774
|
+
unsettled: settle or forget it first). Nothing was sent for a client-named refusal.
|
|
775
|
+
- `{ status: "held", deletions, baseline }`: the host holds this mass delete (`428
|
|
776
|
+
deletions_held`). Nothing was applied. A person confirms the count, and
|
|
777
|
+
`settleBlobWrite(requestId, { acceptDeletes })` with at least `deletions` sends it again; without
|
|
778
|
+
`acceptDeletes` it stays held and nothing is sent, and with fewer it throws
|
|
779
|
+
`HostedBlobDeletionsNotAccepted`, sending nothing. `deleteBlob(..., { acceptDeletes })` sends the
|
|
780
|
+
confirmation with the first attempt.
|
|
781
|
+
- `{ status: "unknown" }`: the host may or may not hold the write.
|
|
782
|
+
|
|
783
|
+
Every result names its `requestId`, `key` and `operation`.
|
|
784
|
+
|
|
785
|
+
**The capability gate.** A blob write needs the bundle's `blobWrites: "allowed"` and its
|
|
786
|
+
`blobWriteBytes` bound, from the same held capabilities answer `write` uses. When the host serves
|
|
787
|
+
the bundle no blob writes (an older host's answer reads as refused), `writeBlob` and `deleteBlob`
|
|
788
|
+
throw `HostedBlobWritesNotServed`, a `HostedRouteNotServed` (`isRouteNotServed` is true), before
|
|
789
|
+
anything is journaled or sent. This differs from `write()`, which answers a bundle without writes
|
|
790
|
+
with the result `refused: read_only`: the blob routes are a newer capability a caller probes for, as
|
|
791
|
+
it probes for any route a host may lack. The checks a send would otherwise fail (the capability, the
|
|
792
|
+
byte bound, the key and the content type) all run against that one answer before the write is
|
|
793
|
+
journaled, and the delivery is handed the same answer, so the two cannot disagree.
|
|
794
|
+
|
|
795
|
+
**Exactly once, never resent after it may have left.** Each blob write is journaled under one
|
|
796
|
+
request identity (with the client's binding, the bundle and workspace and the bytes), durably
|
|
797
|
+
claimed, then sent once. After an unclear answer the client looks the identity up on the host
|
|
798
|
+
(`/outcome`, under the binding recorded with the write) and reports what the host recorded. Unlike a
|
|
799
|
+
document write, an `absent` lookup is never permission to send again: once an attempt may have left,
|
|
800
|
+
a blob write is only ever looked up. A send proven never to have left (the credential provider
|
|
801
|
+
failed or was denied, the signal was already aborted, the identity was malformed, a local check
|
|
802
|
+
failed) is rolled back instead: `writeBlob` throws `HostedBlobWriteNotSent` with its `requestId` and
|
|
803
|
+
`reason`, and the write stays held and resendable (`held: true`), so `settleBlobWrite` or the same
|
|
804
|
+
`writeBlob` call (same `requestId`, same input) sends it. A transport failure after the request was
|
|
805
|
+
handed to the carrier is `unknown`.
|
|
806
|
+
|
|
807
|
+
**Recovery.** `heldBlobWrites()` lists every unsettled blob write (`requestId`, `bundleId`,
|
|
808
|
+
`workspace`, `key`, `operation`, `state`, `attempts`, `resendable`, `hold`, `createdAt`) and sweeps any
|
|
809
|
+
settled one a crash left behind.
|
|
810
|
+
|
|
811
|
+
- `lookupBlobWrite(requestId)` makes one outcome lookup and never sends. A write never sent (or
|
|
812
|
+
proven not applied) answers `{ status: "pending" }` without a request, and a held delete `held`.
|
|
813
|
+
- `settleBlobWrite(requestId, { acceptDeletes? })` submits the write when its state is pending or
|
|
814
|
+
unknown and every attempt is proven not applied (`attempts === notAppliedThrough`); otherwise it
|
|
815
|
+
only looks it up, and an absent lookup is `unknown`.
|
|
816
|
+
- `forgetBlobWrite(requestId)` drops an `unknown` write once one fresh lookup answers `absent`,
|
|
817
|
+
returning `{ status: "forgotten" }`. **The earlier delivery may still commit**; the host's
|
|
818
|
+
compare-and-swap is what keeps a later write from losing that update. Any other record is
|
|
819
|
+
`{ status: "kept", reason: "not_unknown" }` (settle an `in_flight` one first), a lookup that fails,
|
|
820
|
+
is refused or is malformed is `kept` with `lookup_unavailable`, and a lookup that finds the outcome
|
|
821
|
+
settles the write instead. A forgotten identity is no longer held, so `writeBlob` with that same
|
|
822
|
+
`requestId` journals and uploads it again under that identity. That is safe: the host answers a
|
|
823
|
+
delivery it recorded under the identity with the recorded result, and one it did not record is
|
|
824
|
+
checked against the expected pin like any write.
|
|
825
|
+
|
|
826
|
+
All three answer `{ status: "unrecorded", requestId }` when the journal holds no blob write under the
|
|
827
|
+
identity. A reused `requestId` with the same input resumes (settles) the held write; with different
|
|
828
|
+
input it is `refused: invalid_input` and nothing is admitted. Calls are serialized per request
|
|
829
|
+
identity and per key (the journal's guard covers a key across bundles), so concurrent calls of one
|
|
830
|
+
client never trip each other.
|
|
831
|
+
|
|
832
|
+
```ts
|
|
833
|
+
// A service restarting after a crash: settle what it held, forgetting only proven-absent unknowns.
|
|
834
|
+
for (const held of await client.heldBlobWrites()) {
|
|
835
|
+
const settled = await client.settleBlobWrite(held.requestId);
|
|
836
|
+
if (settled.status === "unknown") console.warn(`blob write ${held.requestId} is still unknown`); // forgetBlobWrite once you choose to move on
|
|
837
|
+
}
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
**Reads.** `readBlob(bundleId, key, { pin })` requires the pin: it reads exactly that version and
|
|
841
|
+
content type, verifies length, digest and type, and answers null when the key is no longer at the pin
|
|
842
|
+
(the host's `409`). `blobHead(bundleId, key)` is the explicit lookup of one head (or null), and
|
|
843
|
+
`blobHeads(bundleId)` the whole listing; both read the full verified listing (up to 10,000 heads in
|
|
844
|
+
4 MiB pages), since the host has no single-key head route yet.
|
|
845
|
+
|
|
846
|
+
**Keys.** `isOrdinaryBlobKey` is the one key policy every blob writer uses, this client and the
|
|
847
|
+
CLI's checkout sync alike: a safe key that is not a reserved file (`index.md`, `log.md`) and does not
|
|
848
|
+
sit under Superbee's own record folders (`views/`, `pages/`, their registries, `conventions/`,
|
|
849
|
+
`recipes/`, `references/`, compared case-insensitively). Any other key is refused `invalid_input`
|
|
850
|
+
before any request.
|
|
851
|
+
|
|
852
|
+
**Aborts.** A `signal` aborted before the write is admitted throws its reason and journals nothing;
|
|
853
|
+
that includes an abort while the capabilities are still being read, so a hung credential provider
|
|
854
|
+
never holds the call (or its key) past the caller's abort. Once admitted, a write is held until it
|
|
855
|
+
settles, and an abort before its send leaves it resendable (`HostedBlobWriteNotSent`).
|
|
856
|
+
|
|
857
|
+
**Journal.** The default blob journal is in memory (`createMemoryBlobJournal`), owned by the client.
|
|
858
|
+
Give a blob journal to one client (its `within` clients share it). Two clients over one journal
|
|
859
|
+
coordinate only through its per-key guard: concurrent writes of different identities still settle,
|
|
860
|
+
but two calls on the same request identity from both clients can fail with a raw journal conflict.
|
|
861
|
+
Pass `blobJournal` to keep blob writes across restarts: in Node, `openHostedClientJournal(...).blobs`
|
|
862
|
+
(below). `driveJournaledBlob(journal, context, requestId, mode)` is the owner of the journaled
|
|
863
|
+
sequence (admit, claim, deliver, persist, retire) for a caller with its own journal and premises; its
|
|
864
|
+
`premises.snapshot(key, step)` returns each transaction's guard plus the caller's own metadata
|
|
865
|
+
changes, checked atomically with it.
|
|
866
|
+
|
|
867
|
+
**Limitations.**
|
|
868
|
+
|
|
869
|
+
- Lookups currently need `blobWrites`: a grant downgraded to read-only cannot settle its earlier
|
|
870
|
+
blob writes until the host authorizes the outcome route on its own (a hosted follow-up).
|
|
871
|
+
- A lookup across principals is unverified: a new token of the same principal finds its outcomes
|
|
872
|
+
(the binding travels in the journal), but what another principal's lookup gets is not yet proven.
|
|
873
|
+
- Every journal record of a write carries its bytes, and each transaction validates the record.
|
|
874
|
+
Validation is cached per payload, so a write costs about 0.17 s per MiB in Node (3 MiB about
|
|
875
|
+
0.5 s, 16 MiB about 2.6 s, measured with the in-memory journal); without the cache it was about
|
|
876
|
+
1.7 s per MiB (16 MiB about 28 s). The first validation of a payload still runs on the caller's
|
|
877
|
+
thread in one slice, about 1.2 s for 16 MiB.
|
|
878
|
+
|
|
879
|
+
### Inbox
|
|
880
|
+
|
|
881
|
+
`createHostedInbox(client, { tenantId })` is a person's hosted inbox in one workspace, over the
|
|
882
|
+
same client (its credential, deadline and abort; a `within` client serves too). It reaches the
|
|
883
|
+
attention routes, `POST <prefix>/attention/<operationId>`, and names the workspace in the
|
|
884
|
+
`Superbee-Workspace` header on every request, since a sync sign-in spans all of the person's
|
|
885
|
+
workspaces. Notifications are a hosted capability: a host without these routes answers
|
|
886
|
+
`HostedRouteNotServed`, and nothing here emulates a local inbox.
|
|
887
|
+
|
|
888
|
+
```ts
|
|
889
|
+
import { createHostedInbox } from "@superbee/core/hosted-client";
|
|
890
|
+
|
|
891
|
+
const identity = await client.whoami();
|
|
892
|
+
const inbox = createHostedInbox(client, { tenantId: identity.tenantIds[0]!, via: "my-agent" });
|
|
893
|
+
const receipt = await inbox.request({ recipient: { kind: "principal", principalId: identity.principalId }, kind: "needs_input", note: "Ready for you" });
|
|
894
|
+
const page = await inbox.list({ threadId: receipt.threadId, afterSeq: 0 });
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
The methods are `list`, `threads`, `summary`, `get` (does not mark read), `open` (marks read),
|
|
898
|
+
`ack` (at the revision read), `request` (send) and `cancel` (withdraw your own send), with the
|
|
899
|
+
open contract's shapes (`InboxEntry`, `InboxThread`, `AttentionReceipt` and the inputs). A
|
|
900
|
+
recipient is a principal id; to notify yourself, use `whoami().principalId`. A document target
|
|
901
|
+
names `whoami().targetAuthorityKey` as its `authorityKey` and the document's current version; a
|
|
902
|
+
host that reports no key (the field is absent, as on every `/sync/v1` host today) cannot take one yet.
|
|
903
|
+
|
|
904
|
+
`request` mints one UUIDv7 ticket per send (or takes the one you pass). When an answer is lost or
|
|
905
|
+
the host marks its refusal retryable, it sends the same body under the same ticket again
|
|
906
|
+
(`sendAttempts`, default 3), which the host answers as a replay of the first send, never as a second
|
|
907
|
+
item. A send still unclear after that throws `HostedAttentionOutcomeUnknown` with its `ticket`:
|
|
908
|
+
`request({ ...input, ticket })` within 24 hours settles it. Refusals are
|
|
909
|
+
`HostedAttentionRefusal` (a `RemoteError`) with the catalog's `code` (`attention_disabled`,
|
|
910
|
+
`not_found`, `target_unavailable`, `target_unsupported`, `quota_exceeded`, `request_conflict`,
|
|
911
|
+
`version_conflict`, `ticket_expired` with `serverTime`, `workspace_not_available` and the rest),
|
|
912
|
+
`retryable`, and a message in this client's words: the host's message and hint are never repeated.
|
|
913
|
+
|
|
914
|
+
### Durable intent store (Node only)
|
|
915
|
+
|
|
916
|
+
`openHostedClientJournal` from `@superbee/core/hosted-client/node` is a durable `intentStore`, and its
|
|
917
|
+
`blobs` a durable `blobJournal`, so a short-lived process that exits with a write `unknown` can recover
|
|
918
|
+
it after a restart.
|
|
919
|
+
|
|
920
|
+
```ts
|
|
921
|
+
import { createHostedClient } from "@superbee/core/hosted-client";
|
|
922
|
+
import { openHostedClientJournal } from "@superbee/core/hosted-client/node";
|
|
923
|
+
|
|
924
|
+
const journal = await openHostedClientJournal({ directory: path.join(stateDir, "superbee-writes") });
|
|
925
|
+
const client = createHostedClient({ target, credentials, intentStore: journal, blobJournal: journal.blobs });
|
|
926
|
+
for (const held of await client.heldWrites()) await client.settleWrite(held.requestId);
|
|
927
|
+
for (const held of await client.heldBlobWrites()) await client.settleBlobWrite(held.requestId);
|
|
928
|
+
// ... writes ...
|
|
929
|
+
await journal.close();
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
The journal is a `FileJournaledBackend` directory: an fsynced, checksummed log with snapshot
|
|
933
|
+
compaction. Its on-disk format is a supported compatibility promise: a format bump ships a reader
|
|
934
|
+
for every earlier layout, and an older reader refuses a newer store with `FileJournalFormatError`
|
|
935
|
+
rather than misreading it. The current format is 3 (see `JOURNALED-BACKENDS.md`). Each unsettled write is one metadata row, `hosted-client.write.<requestId>`,
|
|
936
|
+
holding the whole document and the binding it was sent under, so a new process with a new token
|
|
937
|
+
looks it up exactly as the first would have. A settled write is removed. Each unsettled blob write is
|
|
938
|
+
one row of the backend's own blob intent family, with its bytes, under the same transition rules as
|
|
939
|
+
the in-memory journal; a settled one is retired, and since every record of a blob write carries its
|
|
940
|
+
bytes, retiring or forgetting one compacts the store once its log or snapshot is past 1 MiB. Metadata
|
|
941
|
+
a blob write carries may not name the journal's own rows (`hosted-client.*`) or a checkout's
|
|
942
|
+
blob-sync state.
|
|
943
|
+
|
|
944
|
+
- **Ownership.** Open takes an exclusive same-user lock on the directory until `close()`. Another
|
|
945
|
+
process waits `lock.waitMs` (default 5,000 ms) and then gets `FilesystemMutationLockError`. A
|
|
946
|
+
lock left by a process that died on this host is reclaimed.
|
|
947
|
+
- **Local file system only.** The lock lives in a per-user runtime directory on this host, so it
|
|
948
|
+
cannot exclude a process on another host. Keep the journal on a local disk, never on NFS, SMB
|
|
949
|
+
or another shared mount. Open does not detect a network mount; this is the operator's rule.
|
|
950
|
+
- **Privacy.** The journal holds whole documents. A directory open creates is mode 0700, and its
|
|
951
|
+
files 0600. On POSIX, an existing directory that is a symbolic link, is owned by another user,
|
|
952
|
+
or grants its group or others any permission, is refused as `HostedClientJournalError`
|
|
953
|
+
`JOURNAL_DIRECTORY_NOT_PRIVATE`. The check runs again once the lock is held, and a directory
|
|
954
|
+
replaced in between is refused too. The directory is the journal's alone: an existing one may
|
|
955
|
+
hold only `store.log`, `store.snapshot` and `store.snapshot.tmp`, and any other entry (an
|
|
956
|
+
operating system's `.DS_Store` included) is refused as `JOURNAL_NOT_HOSTED_CLIENT`, as is a
|
|
957
|
+
`store.log` that is not a journal's log, and a store file that is a symbolic link, a
|
|
958
|
+
directory, has more than one link, or (on POSIX) belongs to another user. The listing and the
|
|
959
|
+
files are checked again under the lock. Another process of the same user changing the
|
|
960
|
+
directory while it opens is out of scope: in that window the open can truncate a torn or
|
|
961
|
+
foreign log tail.
|
|
962
|
+
- **Marker.** A new journal records `hosted-client.journal = { schemaVersion: 1 }` in its first
|
|
963
|
+
transaction. Open refuses, as `JOURNAL_NOT_HOSTED_CLIENT`, a store with rows but no marker or one
|
|
964
|
+
holding documents, blob files or a checkout's blob-sync state, so it is never pointed at a CLI
|
|
965
|
+
checkout store. A marked journal holding blob write intents is its own and reopens. A marker of another schema is `JOURNAL_SCHEMA_UNSUPPORTED`. Open decides on a read-only
|
|
966
|
+
pass first, so a refused directory's files are left byte for byte. A crash before the marker
|
|
967
|
+
lands leaves an empty store, which the next open marks.
|
|
968
|
+
- **Size.** Once the store is adopted, the log is compacted into the snapshot whenever it grows past the greater of 1 MiB
|
|
969
|
+
(`HOSTED_CLIENT_JOURNAL_COMPACT_FLOOR_BYTES`) and twice the last snapshot, so steady writing keeps
|
|
970
|
+
it near 1 MiB. The snapshot, which holds every write still unsettled, and any one transaction are
|
|
971
|
+
bounded at 256 MiB. A write that would pass that bound is refused before anything is recorded,
|
|
972
|
+
and its `write` throws. Unsettled writes count toward the bound until `settleWrite` resolves them.
|
|
973
|
+
- **Ordering.** Unlike the in-memory store, each call takes effect when its asynchronous steps
|
|
974
|
+
complete, so unawaited overlapping calls on one request identity can land out of call order.
|
|
975
|
+
Await each call before the next, as the client does.
|
|
976
|
+
- **Damage.** `FileJournalCorruptError` (a damaged record before the log's end or a damaged
|
|
977
|
+
snapshot) and `FileJournalFormatError` (a newer format) refuse the open and change no file. A torn
|
|
978
|
+
final record, which was never acknowledged, is truncated. Nothing is repaired automatically:
|
|
979
|
+
moving the directory aside starts an empty journal and loses the writes it held, which can then
|
|
980
|
+
be settled only by reading the documents on the host.
|
|
981
|
+
|
|
982
|
+
### Export download (Node only)
|
|
983
|
+
|
|
984
|
+
`@superbee/core/hosted-client/node` downloads and verifies a hosted bundle's portable export. It
|
|
985
|
+
is a supported public API under the same versioning. It is Node-only because verification
|
|
986
|
+
(`@superbee/core/portable-export`) needs Node; `@superbee/core/hosted-client` never imports it,
|
|
987
|
+
so the client itself stays browser-safe.
|
|
988
|
+
|
|
989
|
+
```ts
|
|
990
|
+
import { bearerCredentials, createHostedClient } from "@superbee/core/hosted-client";
|
|
991
|
+
import { downloadExport, EXPORT_WINDOW_MS } from "@superbee/core/hosted-client/node";
|
|
992
|
+
import { materializeExport } from "@superbee/core/portable-export";
|
|
993
|
+
|
|
994
|
+
const client = createHostedClient({ target, credentials: bearerCredentials(getToken), deadlineMs: EXPORT_WINDOW_MS });
|
|
995
|
+
const verified = await downloadExport(client, "team.notes", { signal });
|
|
996
|
+
const folder = await materializeExport(verified, parentDirectory);
|
|
997
|
+
```
|
|
998
|
+
|
|
999
|
+
`downloadExport(client, bundleId, { signal })` returns a `VerifiedExport`: every document,
|
|
1000
|
+
reserved file and blob at the bundle's latest confirmed revision, read whole and checked against
|
|
1001
|
+
the host's digest manifests. It writes nothing. A stopped, tampered or oversized export never
|
|
1002
|
+
becomes a result; placing the files is the caller's (`materializeExport`, or its own staging). It
|
|
1003
|
+
reads a paged export one page at a time and joins the pages. On a host from before paging it reads
|
|
1004
|
+
the whole archive. A bundle that changes between pages is read again from its first page, up to
|
|
1005
|
+
three attempts, and a busy or stopped page is asked for once more. Create the client with
|
|
1006
|
+
`deadlineMs: EXPORT_WINDOW_MS` (60 s), the host's export window; the default 15 s can cut a large
|
|
1007
|
+
bundle. A `within(slug)` client names the bundle in that workspace.
|
|
1008
|
+
|
|
1009
|
+
Failures are typed:
|
|
1010
|
+
|
|
1011
|
+
- `HostedCarrierError`, `HostedRouteNotServed` (a host without the export route) and
|
|
1012
|
+
`RemoteError` (the host's refusal: `bundle_not_found`, `result_too_large`, `validation_failed`,
|
|
1013
|
+
an unauthenticated bearer and so on), as for the rest of the client.
|
|
1014
|
+
- `ExportArchiveError` (`problem`): the verifier refused the archive. `incomplete` means the host
|
|
1015
|
+
stopped it, and the same request may succeed.
|
|
1016
|
+
- `HostedExportError` (`problem`): `stopped` (the answer's body failed part way), `too_large`
|
|
1017
|
+
(past `MAX_EXPORT_BYTES`) or `source_changed` (the bundle kept changing between pages).
|
|
1018
|
+
- The signal's reason, once `signal` or the client's own signal aborts.
|
|
1019
|
+
|
|
1020
|
+
## Release compatibility
|
|
1021
|
+
|
|
1022
|
+
This migration describes the source after the Windows extraction. Previously published versions
|
|
1023
|
+
are unchanged. Consult the release notes for the first affected version before upgrading a pinned
|
|
1024
|
+
consumer. CLI and engine version numbers are independent; do not infer matching embedded code from
|
|
1025
|
+
package names or version labels.
|
|
1026
|
+
|
|
1027
|
+
[Source and contribution guide](https://github.com/Holaxis-ai/superbee)
|
|
1028
|
+
|
|
1029
|
+
### Node portable export boundary
|
|
1030
|
+
|
|
1031
|
+
`@superbee/core/portable-export` (also bundled as `superbee/portable-export`) exposes the same
|
|
1032
|
+
store-only archive verifier used by the CLI: `verifyExport`, `ExportPageChain` and
|
|
1033
|
+
`joinExportPages`. The page chain charges the sum of complete ZIP sizes, including manifests
|
|
1034
|
+
and headers, against `MAX_EXPORT_BYTES` before retaining a page; returned `bytes` counts only
|
|
1035
|
+
stored bundle content. It performs no acquisition, sign-in or network access. A complete accepted
|
|
1036
|
+
handle can be passed to `materializeExport(handle, existingPrivateParent)` to create a fresh
|
|
1037
|
+
private directory; callers own its cleanup and isolation while publishing. No target is replaced.
|
|
1038
|
+
`exportProvenance(handle)` returns exact ordered raw-manifest SHA-256/size evidence plus a combined
|
|
1039
|
+
inventory SHA-256. Keep that evidence outside the directory and private when it identifies a
|
|
1040
|
+
private source. Handles cannot be reconstructed from JSON or moved between independently bundled
|
|
1041
|
+
module instances; modified handles are refused. This surface requires Node, not a browser or Worker.
|
|
1042
|
+
|
|
1043
|
+
## Built-in recipes and Kind authoring
|
|
1044
|
+
|
|
1045
|
+
`@superbee/core/builtin-recipes` is an opt-in catalog: `builtinNames()` lists names,
|
|
1046
|
+
`builtinRecipeFiles(name)` returns detached source files, and `loadBuiltinRecipe(name)` returns
|
|
1047
|
+
the existing parser's result or `null` for an unknown name. The generic parser, package root,
|
|
1048
|
+
and `initBundle` do not import the catalog or install recipes.
|
|
1049
|
+
|
|
1050
|
+
`@superbee/core/kind-authoring` provides inference, forecasts, dismissal classification, templates,
|
|
1051
|
+
and guarded draft/apply/dismiss operations on a caller-selected `Bundle` backend. An outside app
|
|
1052
|
+
can use the existing backend and mutation seams without a CLI subprocess:
|
|
1053
|
+
|
|
1054
|
+
```ts
|
|
1055
|
+
import { MemoryBackend } from "@superbee/core/memory-backend";
|
|
1056
|
+
import { query } from "@superbee/core/engine";
|
|
1057
|
+
import { mutateDocument } from "@superbee/core/document-mutation";
|
|
1058
|
+
import { buildKindRegistry } from "@superbee/core/kinds";
|
|
1059
|
+
import { loadBuiltinRecipe } from "@superbee/core/builtin-recipes";
|
|
1060
|
+
import { installRecipe } from "@superbee/core/recipe-lifecycle";
|
|
1061
|
+
import { kindInstanceCandidate, prepareKindDraft, applyKindDraft } from "@superbee/core/kind-authoring";
|
|
1062
|
+
|
|
1063
|
+
const backend = new MemoryBackend(), bundle = { root: "memory://project", backend };
|
|
1064
|
+
await backend.writeReserved("", "index.md", "---\nokf_version: '0.2'\n---\n# Project\n");
|
|
1065
|
+
const loaded = loadBuiltinRecipe("work-tracking");
|
|
1066
|
+
if (!loaded?.ok) throw new Error("Recipe unavailable");
|
|
1067
|
+
await installRecipe(bundle, loaded.recipe);
|
|
1068
|
+
const registry = buildKindRegistry(await query(backend, { prefix: "conventions/" }));
|
|
1069
|
+
const task = registry.kinds.get("Task")!;
|
|
1070
|
+
const candidate = kindInstanceCandidate(task, { title: "First task", superbee_progress_status: "todo" });
|
|
1071
|
+
await mutateDocument({ bundle, id: "tasks/first", mode: "create-only", registry, strict: true,
|
|
1072
|
+
buildCandidate: () => candidate });
|
|
1073
|
+
|
|
1074
|
+
// Once an ungoverned type has existing instances, inspect its draft before applying.
|
|
1075
|
+
const text = { inferredBody: () => "The accepted shape of existing research records.\n" };
|
|
1076
|
+
const plan = await prepareKindDraft(bundle, "Research", text); // requires existing Research docs
|
|
1077
|
+
// Present candidateDoc, warnings and promotions; after acceptance:
|
|
1078
|
+
await applyKindDraft(bundle, "Research", plan.token, text);
|
|
1079
|
+
```
|
|
1080
|
+
|
|
1081
|
+
`kindInstanceCandidate` accepts already-typed field values. Existing validators own their meaning;
|
|
1082
|
+
there is no CLI flag coercion or path-alias resolution. Supply a canonical id and use strict
|
|
1083
|
+
create-only mutation. The template keeps `type` equal to `governs`, preserves own prototype-shaped
|
|
1084
|
+
fields, scaffolds sections only when body is omitted, and retains an explicit empty body.
|
|
1085
|
+
|
|
1086
|
+
Public draft/apply/dismiss operations trim type names and refuse blank or non-string names before
|
|
1087
|
+
any backend access, matching convention parsing. `prepareKindDraft` selects a catalog definition
|
|
1088
|
+
or infers from instances. Its token binds candidate
|
|
1089
|
+
bytes (including supplied `inferredBody`/`inferredDescription`), edition, presence statistics and
|
|
1090
|
+
warning forecast. Pass the same prose options to `applyKindDraft`, which recomputes the plan and
|
|
1091
|
+
checks edition and declaration-free redraft eligibility against every fresh CAS head. New
|
|
1092
|
+
conventions are expect-absent creates; accepted redrafts replace dismissal prose in place and
|
|
1093
|
+
report dropped links. `dismissKind` accepts presentation-owned description/body, records no schema
|
|
1094
|
+
obligations, refuses a real schema, and is idempotent. Definition writes retain recipe
|
|
1095
|
+
installation's generation-clock opt-out. `KindAuthoringError.reason` identifies domain refusals;
|
|
1096
|
+
normal mutation/storage errors retain their types.
|
|
1097
|
+
|
|
1098
|
+
Presentations with an established mutation adapter may pass the options from
|
|
1099
|
+
`prepareKindDraftMutation` or `prepareKindDismissal` through that adapter. These are the same
|
|
1100
|
+
specifications used by public apply/dismiss; keep preparation adjacent to mutation. Outside apps
|
|
1101
|
+
normally use `applyKindDraft`/`dismissKind`, which own the read-and-apply sequence. No cross-document
|
|
1102
|
+
transaction or authorization boundary is added.
|
|
1103
|
+
|
|
1104
|
+
The packed proof runs the public workflow over `MemoryBackend` in Node and a browser bundle without
|
|
1105
|
+
Node globals. This proves source/package availability. Hosted provisioning routes, MCP modeling
|
|
1106
|
+
tools, and publication of a core version remain separate adoption and release decisions.
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
## Declarative JSON sources
|
|
1110
|
+
|
|
1111
|
+
`@superbee/core/source-profiles` exports `getSourceProfile(id)`, frozen `SOURCE_PROFILES` metadata,
|
|
1112
|
+
and `restJsonProfile` (version 1). The catalog contains only `rest-json`; GitHub
|
|
1113
|
+
normalization ships separately in [`@superbee/integration-github`](https://github.com/Holaxis-ai/superbee-plugins/tree/main/integrations/github). Core also exports the existing
|
|
1114
|
+
`nextLinkTarget` parser, `SourcePluginHelpers` type and exact `SOURCE_PLUGIN_CORE_VERSION` for
|
|
1115
|
+
explicit host composition. The profile accepts a parsed `ConnectorSource` as its spec;
|
|
1116
|
+
`source.request` describes provider data, while `source.fields` maps normalized paths to Kind fields.
|
|
1117
|
+
It runs only through `observeSource` and the caller's recorded or credential-injecting transport.
|
|
1118
|
+
Installing a Connector does not run it.
|
|
1119
|
+
|
|
1120
|
+
The optional, pinned request block requires `host`, `method`, `path` and `items` (empty string for
|
|
1121
|
+
a root array). `query`, `body` and `headers` contain scalar literals or whole `{binding}`, `{cursor}`,
|
|
1122
|
+
`{page}` and `{watermark}` values. Path placeholders occupy whole segments. `request.fields` maps
|
|
1123
|
+
normalized dotted paths to `{from, type?}` or `{const}`; types are `text`, `integer`, `date`
|
|
1124
|
+
(ISO timestamp), `epoch-date` (seconds to ISO timestamp), and `url` (HTTP(S)). Presence-only `skip`
|
|
1125
|
+
rules count skipped records. No executable expressions or synthesis are supported.
|
|
1126
|
+
|
|
1127
|
+
Paging kinds are `none`, `cursor`, `link-header`, `offset` and `page-number`. Cursor paging uses
|
|
1128
|
+
`cursor_path` (opaque token, placed through `query`) or `next_path` (URL). Numeric paging requires
|
|
1129
|
+
`size` and `page_param` or `offset_param`, and restarts from the beginning after interruption.
|
|
1130
|
+
`watermark: {field, param, overlap_seconds}` applies overlap between passes and keeps the pass's
|
|
1131
|
+
starting watermark fixed across pages. `complete` is `always`, `never`, or `{when_path, equals}`;
|
|
1132
|
+
the engine additionally requires an uninterrupted complete-mode pass before counting absences.
|
|
1133
|
+
Optional `errors` sets (`deferred`, `credential_refused`, `not_found`) and `rate.requests` narrow
|
|
1134
|
+
the run's status interpretation and request budget.
|
|
1135
|
+
|
|
1136
|
+
Existing declaration pins without a request block remain unchanged. Credentialed engine consumers
|
|
1137
|
+
must now supply a request declaration: `declaration_required` refuses a missing one before any
|
|
1138
|
+
transport call. Parsing and installation remain inert. Provider read paths, read-shaped POST paths,
|
|
1139
|
+
headers and paging forms default to the legacy Core-owned, deeply frozen table and are revalidated at every run; caller rules
|
|
1140
|
+
can only narrow them. Callback URL/response views are isolated from execution; changing a view
|
|
1141
|
+
cannot rewrite the request. Core rechecks its private run authority after narrowing callbacks.
|
|
1142
|
+
Keyless legacy profiles retain the declared-domain and robots rules.
|
|
1143
|
+
|
|
1144
|
+
GitHub's repository issues GET, version header and alternate numeric pagination form follow
|
|
1145
|
+
[issues documentation](https://docs.github.com/en/rest/issues/issues) and
|
|
1146
|
+
[pagination documentation](https://docs.github.com/en/rest/using-the-rest-api/using-pagination-in-the-rest-api).
|
|
1147
|
+
Numeric `/repositories/{id}/issues` paging additionally requires first-page repository metadata
|
|
1148
|
+
that proves the id belongs to the bound owner/repository and stays fixed for that pass. The issues
|
|
1149
|
+
listing does not guarantee that metadata; a Link alone cannot establish it. Without proof the
|
|
1150
|
+
engine refuses that alternate form. Its metadata proof is scoped to the current run, so resuming
|
|
1151
|
+
an interrupted alternate numeric URL in a later run also refuses; restart from the bound path.
|
|
1152
|
+
The conditional metadata test is not live-provider
|
|
1153
|
+
qualification. Same bound `/repos/{owner}/{repo}/issues` pagination remains usable.
|
|
1154
|
+
|
|
1155
|
+
GraphQL declarations use the real `graphql` syntax parser: one query operation, no variable defaults,
|
|
1156
|
+
and bound String/ID inputs. GraphQL execution is deferred to the later GraphQL profile slice:
|
|
1157
|
+
`graphql_scope_unsupported` refuses even a well-formed read body before transport. Syntax alone
|
|
1158
|
+
does not establish resource-scoped traversal. Mutations and malformed extension bodies are refused
|
|
1159
|
+
by the engine wrapper; caller hooks cannot enable them.
|
|
1160
|
+
|
|
1161
|
+
## Explicit local bound GET policies
|
|
1162
|
+
|
|
1163
|
+
An optional `request.read_policy` requests a new narrowly bound GET scope without adding a provider
|
|
1164
|
+
adapter or changing the legacy endpoint table. The local operator must review the exact endpoint,
|
|
1165
|
+
query and provider read semantics before accepting its feed binding. GET alone is not proof of
|
|
1166
|
+
absence of side effects. A package or policy declaration does not approve itself.
|
|
1167
|
+
|
|
1168
|
+
For a repository pull list, the existing request is still the only endpoint/query/header declaration:
|
|
1169
|
+
|
|
1170
|
+
```yaml
|
|
1171
|
+
host: api.github.com
|
|
1172
|
+
method: GET
|
|
1173
|
+
path: '/repos/{owner}/{repo}/pulls'
|
|
1174
|
+
items: ''
|
|
1175
|
+
query: {state: all}
|
|
1176
|
+
headers: {accept: application/vnd.github+json, x-github-api-version: '2026-03-10'}
|
|
1177
|
+
paging: {kind: page-number, page_param: page, size_param: per_page, size: 30}
|
|
1178
|
+
read_policy:
|
|
1179
|
+
contract: 1
|
|
1180
|
+
kind: bound-get
|
|
1181
|
+
bindings: {owner: segment, repo: segment}
|
|
1182
|
+
required_permissions: {pull_requests: read}
|
|
1183
|
+
header_provider: github
|
|
1184
|
+
```
|
|
1185
|
+
|
|
1186
|
+
Use identity resource `https://github.com/{owner}/{repo}/pulls`. For a separately bound detail
|
|
1187
|
+
source, use resource `https://github.com/{owner}/{repo}/pull/{number}`, request path
|
|
1188
|
+
`/repos/{owner}/{repo}/pulls/{number}`, `number: positive-integer`, no list query and
|
|
1189
|
+
`paging: {kind: none}`. Detail object normalization belongs in the integration; no Core PR adapter
|
|
1190
|
+
is added. GitHub documents both [GET endpoints and permission minima](https://docs.github.com/en/rest/pulls/pulls).
|
|
1191
|
+
`pull_requests: read` is a common minimum. GitHub App declarations must include the requested
|
|
1192
|
+
permissions at read or write; write grants are disclosed and do not authorize source writes.
|
|
1193
|
+
Opaque token grants cannot be inspected by Core: minima are declarations and the provider enforces
|
|
1194
|
+
actual credential access.
|
|
1195
|
+
|
|
1196
|
+
Contract 1 permits one exact host/path, GET only, no body, GraphQL or watermark. Every identity
|
|
1197
|
+
parameter appears once as an entire request path segment. Binding keys equal the identity
|
|
1198
|
+
placeholder set; `segment` uses the existing safe resource grammar, while `positive-integer` is
|
|
1199
|
+
canonical decimal 1 through `Number.MAX_SAFE_INTEGER`. The public resolver validates the identity
|
|
1200
|
+
with the existing parser and requires parameters to render `bound.resource` byte for byte, even
|
|
1201
|
+
when a library caller constructs `BoundFeed` directly.
|
|
1202
|
+
|
|
1203
|
+
Queries require every exact literal or whole bound-value placeholder once. Cursor, watermark and
|
|
1204
|
+
free page placeholders are refused. Paging supports only absent/none or page-number. Page/size
|
|
1205
|
+
names must be distinct and outside query; none permits no inactive fields, and page-number permits
|
|
1206
|
+
only kind/page_param/size_param/size. Every request requires one positive page and, when named,
|
|
1207
|
+
one exact pinned size. New policies inherit no provider numeric aliases or cursor forms.
|
|
1208
|
+
|
|
1209
|
+
Declared headers are required once with exact values at every hop; omission, case duplicates and
|
|
1210
|
+
undeclared headers refuse. An absent header map selects the existing `accept: application/json`
|
|
1211
|
+
default; an explicit map is complete. Incremental engine validators are the only profile-header
|
|
1212
|
+
exception, and complete mode refuses them. Generated user-agent/accept-encoding remain engine/transport
|
|
1213
|
+
owned. Existing forbidden credential/override headers and transport stripping still apply.
|
|
1214
|
+
`PROVIDER_HEADER_RULES` is separate from `AUTH_PROVIDERS`: GitHub permits only an exact calendar-date
|
|
1215
|
+
`x-github-api-version` literal toward `api.github.com`. For anonymous sources use `auth: {kind: none}`,
|
|
1216
|
+
`domains: [api.github.com]`, and omit permission minima. Header-provider selection never creates a
|
|
1217
|
+
credential destination or bypasses keyless robots rules.
|
|
1218
|
+
|
|
1219
|
+
`inspectSourceReadPolicy(connectorAuthAndDomains, bound)` from `@superbee/core/connectors` is inert
|
|
1220
|
+
and throws on an invalid declaration/binding. It returns normalized scope and a digest binding
|
|
1221
|
+
request, source, resource, parameters, auth, domains and resolved policy/header semantics. The trusted
|
|
1222
|
+
embedding host supplies `readPolicyApproval: {contract: 1, digest}` to `observeSource` only after its
|
|
1223
|
+
own exact admission. Missing/stale approval refuses before profile callbacks, state replay or transport.
|
|
1224
|
+
The digest is publicly computable: it is a host assertion, not an unforgeable capability or proof of a
|
|
1225
|
+
human click. Caller rules can only narrow Core authority.
|
|
1226
|
+
|
|
1227
|
+
`SourceFixture.readPolicyApproval` forwards the same explicit assertion to the public recorded
|
|
1228
|
+
harness. It is fixture-only and grants no production consent. The CLI `source test` JSON schema does
|
|
1229
|
+
not carry that field and does not auto-approve new policies; use supported public Core/harness APIs
|
|
1230
|
+
for those recorded tests. Existing legacy fixtures require no new argument.
|
|
1231
|
+
|
|
1232
|
+
The local CLI previews new-policy bindings without importing code or reading credentials and uses
|
|
1233
|
+
existing `source bind --accept` to record exact consent. Any change requires explicit rebind and
|
|
1234
|
+
resets acquired state. Unchanged legacy issue declarations retain their historical pin and consent
|
|
1235
|
+
projections. Managed consumers remain denied without separately deployed operator admission; this
|
|
1236
|
+
public API does not establish deployed managed-host adoption. Trusted plugin code still runs with
|
|
1237
|
+
ambient process authority; the guarded transport is not a sandbox.
|
|
1238
|
+
|
|
1239
|
+
## Declared Kind model
|
|
1240
|
+
|
|
1241
|
+
`@superbee/core/domain-model` exports `projectDomainModelV0(registry)` and its
|
|
1242
|
+
readonly input/output types. Pass a canonical registry from `buildKindRegistry`
|
|
1243
|
+
(`@superbee/core/kinds`) or `loadKinds`; the projection performs no parsing or I/O.
|
|
1244
|
+
It returns deeply frozen JSON with deterministically ordered Kind definitions,
|
|
1245
|
+
declared relationships, registry warnings and informational outside-scope endpoints.
|
|
1246
|
+
Structurally malformed inputs throw `TypeError`.
|
|
1247
|
+
|
|
1248
|
+
The v0 shape preserves authored array order and warning metadata. It does not
|
|
1249
|
+
include `fields.types` or derived `freshnessHorizonMs`; adapters that already add
|
|
1250
|
+
these fields keep that enrichment. Projection does not establish snapshot coherence,
|
|
1251
|
+
authorization or permission to disclose a bundle's declarations.
|
|
1252
|
+
|
|
1253
|
+
See [explicit recipe-set structural preflight](recipe-set-preflight.md) for the opt-in read-only composition checker and its evidence limits.
|
|
1254
|
+
|
|
1255
|
+
Finite data-only local observations use [Snapshot Sources](SNAPSHOT-SOURCES.md) through the existing source-engine and record-overlay owners.
|