@aigne/aos 0.2.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/LICENSE.md +26 -0
- package/dist/agent-dsl.cjs +116 -0
- package/dist/agent-dsl.d.cts +31 -0
- package/dist/agent-dsl.d.cts.map +1 -0
- package/dist/agent-dsl.d.mts +31 -0
- package/dist/agent-dsl.d.mts.map +1 -0
- package/dist/agent-dsl.mjs +115 -0
- package/dist/agent-dsl.mjs.map +1 -0
- package/dist/arc-compat.cjs +125 -0
- package/dist/arc-compat.d.cts +17 -0
- package/dist/arc-compat.d.cts.map +1 -0
- package/dist/arc-compat.d.mts +17 -0
- package/dist/arc-compat.d.mts.map +1 -0
- package/dist/arc-compat.mjs +123 -0
- package/dist/arc-compat.mjs.map +1 -0
- package/dist/auto-surface/compiler.cjs +36 -0
- package/dist/auto-surface/compiler.d.cts +7 -0
- package/dist/auto-surface/compiler.d.cts.map +1 -0
- package/dist/auto-surface/compiler.d.mts +7 -0
- package/dist/auto-surface/compiler.d.mts.map +1 -0
- package/dist/auto-surface/compiler.mjs +37 -0
- package/dist/auto-surface/compiler.mjs.map +1 -0
- package/dist/auto-surface/definition-dsl.cjs +333 -0
- package/dist/auto-surface/definition-dsl.d.cts +41 -0
- package/dist/auto-surface/definition-dsl.d.cts.map +1 -0
- package/dist/auto-surface/definition-dsl.d.mts +41 -0
- package/dist/auto-surface/definition-dsl.d.mts.map +1 -0
- package/dist/auto-surface/definition-dsl.mjs +332 -0
- package/dist/auto-surface/definition-dsl.mjs.map +1 -0
- package/dist/auto-surface/fragments.cjs +177 -0
- package/dist/auto-surface/fragments.d.cts +23 -0
- package/dist/auto-surface/fragments.d.cts.map +1 -0
- package/dist/auto-surface/fragments.d.mts +23 -0
- package/dist/auto-surface/fragments.d.mts.map +1 -0
- package/dist/auto-surface/fragments.mjs +175 -0
- package/dist/auto-surface/fragments.mjs.map +1 -0
- package/dist/auto-surface/index.d.mts +9 -0
- package/dist/auto-surface/layering.cjs +43 -0
- package/dist/auto-surface/layering.d.cts +7 -0
- package/dist/auto-surface/layering.d.cts.map +1 -0
- package/dist/auto-surface/layering.d.mts +7 -0
- package/dist/auto-surface/layering.d.mts.map +1 -0
- package/dist/auto-surface/layering.mjs +43 -0
- package/dist/auto-surface/layering.mjs.map +1 -0
- package/dist/auto-surface/projection.cjs +11 -0
- package/dist/auto-surface/projection.d.cts +11 -0
- package/dist/auto-surface/projection.d.cts.map +1 -0
- package/dist/auto-surface/projection.d.mts +11 -0
- package/dist/auto-surface/projection.d.mts.map +1 -0
- package/dist/auto-surface/projection.mjs +12 -0
- package/dist/auto-surface/projection.mjs.map +1 -0
- package/dist/auto-surface/projectors/settings-shell.cjs +114 -0
- package/dist/auto-surface/projectors/settings-shell.d.cts +10 -0
- package/dist/auto-surface/projectors/settings-shell.d.cts.map +1 -0
- package/dist/auto-surface/projectors/settings-shell.d.mts +10 -0
- package/dist/auto-surface/projectors/settings-shell.d.mts.map +1 -0
- package/dist/auto-surface/projectors/settings-shell.mjs +114 -0
- package/dist/auto-surface/projectors/settings-shell.mjs.map +1 -0
- package/dist/auto-surface/scenario.cjs +38 -0
- package/dist/auto-surface/scenario.d.cts +31 -0
- package/dist/auto-surface/scenario.d.cts.map +1 -0
- package/dist/auto-surface/scenario.d.mts +31 -0
- package/dist/auto-surface/scenario.d.mts.map +1 -0
- package/dist/auto-surface/scenario.mjs +37 -0
- package/dist/auto-surface/scenario.mjs.map +1 -0
- package/dist/auto-surface/types.cjs +9 -0
- package/dist/auto-surface/types.d.cts +153 -0
- package/dist/auto-surface/types.d.cts.map +1 -0
- package/dist/auto-surface/types.d.mts +153 -0
- package/dist/auto-surface/types.d.mts.map +1 -0
- package/dist/auto-surface/types.mjs +9 -0
- package/dist/auto-surface/types.mjs.map +1 -0
- package/dist/auto-surface/value-dsl.cjs +80 -0
- package/dist/auto-surface/value-dsl.d.cts +24 -0
- package/dist/auto-surface/value-dsl.d.cts.map +1 -0
- package/dist/auto-surface/value-dsl.d.mts +24 -0
- package/dist/auto-surface/value-dsl.d.mts.map +1 -0
- package/dist/auto-surface/value-dsl.mjs +79 -0
- package/dist/auto-surface/value-dsl.mjs.map +1 -0
- package/dist/blocklet/activation.cjs +94 -0
- package/dist/blocklet/activation.d.cts +30 -0
- package/dist/blocklet/activation.d.cts.map +1 -0
- package/dist/blocklet/activation.d.mts +30 -0
- package/dist/blocklet/activation.d.mts.map +1 -0
- package/dist/blocklet/activation.mjs +94 -0
- package/dist/blocklet/activation.mjs.map +1 -0
- package/dist/blocklet/did-space-registry.cjs +282 -0
- package/dist/blocklet/did-space-registry.d.cts +82 -0
- package/dist/blocklet/did-space-registry.d.cts.map +1 -0
- package/dist/blocklet/did-space-registry.d.mts +82 -0
- package/dist/blocklet/did-space-registry.d.mts.map +1 -0
- package/dist/blocklet/did-space-registry.mjs +281 -0
- package/dist/blocklet/did-space-registry.mjs.map +1 -0
- package/dist/blocklet/handlers.cjs +66 -0
- package/dist/blocklet/handlers.d.cts +16 -0
- package/dist/blocklet/handlers.d.cts.map +1 -0
- package/dist/blocklet/handlers.d.mts +16 -0
- package/dist/blocklet/handlers.d.mts.map +1 -0
- package/dist/blocklet/handlers.mjs +65 -0
- package/dist/blocklet/handlers.mjs.map +1 -0
- package/dist/blocklet/index.d.mts +7 -0
- package/dist/blocklet/manifest.d.cts +2 -0
- package/dist/blocklet/manifest.d.mts +2 -0
- package/dist/blocklet/manifest.mjs +3 -0
- package/dist/blocklet/registry.cjs +447 -0
- package/dist/blocklet/registry.d.cts +197 -0
- package/dist/blocklet/registry.d.cts.map +1 -0
- package/dist/blocklet/registry.d.mts +197 -0
- package/dist/blocklet/registry.d.mts.map +1 -0
- package/dist/blocklet/registry.mjs +441 -0
- package/dist/blocklet/registry.mjs.map +1 -0
- package/dist/blocklet/static-render.cjs +183 -0
- package/dist/blocklet/static-render.d.cts +55 -0
- package/dist/blocklet/static-render.d.cts.map +1 -0
- package/dist/blocklet/static-render.d.mts +55 -0
- package/dist/blocklet/static-render.d.mts.map +1 -0
- package/dist/blocklet/static-render.mjs +181 -0
- package/dist/blocklet/static-render.mjs.map +1 -0
- package/dist/blocklet/types.d.cts +70 -0
- package/dist/blocklet/types.d.cts.map +1 -0
- package/dist/blocklet/types.d.mts +70 -0
- package/dist/blocklet/types.d.mts.map +1 -0
- package/dist/devices/chain.cjs +127 -0
- package/dist/devices/chain.d.cts +48 -0
- package/dist/devices/chain.d.cts.map +1 -0
- package/dist/devices/chain.d.mts +48 -0
- package/dist/devices/chain.d.mts.map +1 -0
- package/dist/devices/chain.mjs +125 -0
- package/dist/devices/chain.mjs.map +1 -0
- package/dist/dsl-shared.cjs +290 -0
- package/dist/dsl-shared.d.cts +54 -0
- package/dist/dsl-shared.d.cts.map +1 -0
- package/dist/dsl-shared.d.mts +54 -0
- package/dist/dsl-shared.d.mts.map +1 -0
- package/dist/dsl-shared.mjs +287 -0
- package/dist/dsl-shared.mjs.map +1 -0
- package/dist/http/afs-mcp.cjs +85 -0
- package/dist/http/afs-mcp.d.cts +14 -0
- package/dist/http/afs-mcp.d.cts.map +1 -0
- package/dist/http/afs-mcp.d.mts +14 -0
- package/dist/http/afs-mcp.d.mts.map +1 -0
- package/dist/http/afs-mcp.mjs +86 -0
- package/dist/http/afs-mcp.mjs.map +1 -0
- package/dist/http/afs-raw.cjs +264 -0
- package/dist/http/afs-raw.d.cts +31 -0
- package/dist/http/afs-raw.d.cts.map +1 -0
- package/dist/http/afs-raw.d.mts +31 -0
- package/dist/http/afs-raw.d.mts.map +1 -0
- package/dist/http/afs-raw.mjs +264 -0
- package/dist/http/afs-raw.mjs.map +1 -0
- package/dist/http/afs-rest.cjs +30 -0
- package/dist/http/afs-rest.d.cts +21 -0
- package/dist/http/afs-rest.d.cts.map +1 -0
- package/dist/http/afs-rest.d.mts +21 -0
- package/dist/http/afs-rest.d.mts.map +1 -0
- package/dist/http/afs-rest.mjs +31 -0
- package/dist/http/afs-rest.mjs.map +1 -0
- package/dist/http/afs-rpc-wire.cjs +271 -0
- package/dist/http/afs-rpc-wire.d.cts +195 -0
- package/dist/http/afs-rpc-wire.d.cts.map +1 -0
- package/dist/http/afs-rpc-wire.d.mts +195 -0
- package/dist/http/afs-rpc-wire.d.mts.map +1 -0
- package/dist/http/afs-rpc-wire.mjs +262 -0
- package/dist/http/afs-rpc-wire.mjs.map +1 -0
- package/dist/http/afs-rpc.cjs +729 -0
- package/dist/http/afs-rpc.d.cts +103 -0
- package/dist/http/afs-rpc.d.cts.map +1 -0
- package/dist/http/afs-rpc.d.mts +103 -0
- package/dist/http/afs-rpc.d.mts.map +1 -0
- package/dist/http/afs-rpc.mjs +729 -0
- package/dist/http/afs-rpc.mjs.map +1 -0
- package/dist/http/afs-upload.cjs +116 -0
- package/dist/http/afs-upload.d.cts +40 -0
- package/dist/http/afs-upload.d.cts.map +1 -0
- package/dist/http/afs-upload.d.mts +40 -0
- package/dist/http/afs-upload.d.mts.map +1 -0
- package/dist/http/afs-upload.mjs +116 -0
- package/dist/http/afs-upload.mjs.map +1 -0
- package/dist/http/aup-client.cjs +60 -0
- package/dist/http/aup-client.d.cts +22 -0
- package/dist/http/aup-client.d.cts.map +1 -0
- package/dist/http/aup-client.d.mts +22 -0
- package/dist/http/aup-client.d.mts.map +1 -0
- package/dist/http/aup-client.mjs +59 -0
- package/dist/http/aup-client.mjs.map +1 -0
- package/dist/http/aup-event.cjs +100 -0
- package/dist/http/aup-event.d.cts +38 -0
- package/dist/http/aup-event.d.cts.map +1 -0
- package/dist/http/aup-event.d.mts +38 -0
- package/dist/http/aup-event.d.mts.map +1 -0
- package/dist/http/aup-event.mjs +101 -0
- package/dist/http/aup-event.mjs.map +1 -0
- package/dist/http/auth-context.cjs +66 -0
- package/dist/http/auth-context.d.cts +64 -0
- package/dist/http/auth-context.d.cts.map +1 -0
- package/dist/http/auth-context.d.mts +64 -0
- package/dist/http/auth-context.d.mts.map +1 -0
- package/dist/http/auth-context.mjs +66 -0
- package/dist/http/auth-context.mjs.map +1 -0
- package/dist/http/csrf.cjs +50 -0
- package/dist/http/csrf.d.cts +8 -0
- package/dist/http/csrf.d.cts.map +1 -0
- package/dist/http/csrf.d.mts +8 -0
- package/dist/http/csrf.d.mts.map +1 -0
- package/dist/http/csrf.mjs +49 -0
- package/dist/http/csrf.mjs.map +1 -0
- package/dist/http/handlers.cjs +106 -0
- package/dist/http/handlers.d.cts +39 -0
- package/dist/http/handlers.d.cts.map +1 -0
- package/dist/http/handlers.d.mts +39 -0
- package/dist/http/handlers.d.mts.map +1 -0
- package/dist/http/handlers.mjs +104 -0
- package/dist/http/handlers.mjs.map +1 -0
- package/dist/http/index.d.mts +1 -0
- package/dist/http/ingest-facade.cjs +315 -0
- package/dist/http/ingest-facade.mjs +314 -0
- package/dist/http/ingest-facade.mjs.map +1 -0
- package/dist/http/raw-url.cjs +136 -0
- package/dist/http/raw-url.d.cts +54 -0
- package/dist/http/raw-url.d.cts.map +1 -0
- package/dist/http/raw-url.d.mts +54 -0
- package/dist/http/raw-url.d.mts.map +1 -0
- package/dist/http/raw-url.mjs +136 -0
- package/dist/http/raw-url.mjs.map +1 -0
- package/dist/http/request-context.cjs +178 -0
- package/dist/http/request-context.d.cts +76 -0
- package/dist/http/request-context.d.cts.map +1 -0
- package/dist/http/request-context.d.mts +76 -0
- package/dist/http/request-context.d.mts.map +1 -0
- package/dist/http/request-context.mjs +179 -0
- package/dist/http/request-context.mjs.map +1 -0
- package/dist/index.cjs +197 -0
- package/dist/index.d.cts +63 -0
- package/dist/index.d.mts +68 -0
- package/dist/index.mjs +61 -0
- package/dist/mcp/index.cjs +9 -0
- package/dist/mcp/index.d.cts +5 -0
- package/dist/mcp/index.d.mts +5 -0
- package/dist/mcp/index.mjs +6 -0
- package/dist/mcp/prompts.cjs +17 -0
- package/dist/mcp/prompts.d.cts +8 -0
- package/dist/mcp/prompts.d.cts.map +1 -0
- package/dist/mcp/prompts.d.mts +8 -0
- package/dist/mcp/prompts.d.mts.map +1 -0
- package/dist/mcp/prompts.mjs +17 -0
- package/dist/mcp/prompts.mjs.map +1 -0
- package/dist/mcp/resources.cjs +22 -0
- package/dist/mcp/resources.d.cts +8 -0
- package/dist/mcp/resources.d.cts.map +1 -0
- package/dist/mcp/resources.d.mts +8 -0
- package/dist/mcp/resources.d.mts.map +1 -0
- package/dist/mcp/resources.mjs +22 -0
- package/dist/mcp/resources.mjs.map +1 -0
- package/dist/mcp/server.cjs +51 -0
- package/dist/mcp/server.d.cts +20 -0
- package/dist/mcp/server.d.cts.map +1 -0
- package/dist/mcp/server.d.mts +20 -0
- package/dist/mcp/server.d.mts.map +1 -0
- package/dist/mcp/server.mjs +52 -0
- package/dist/mcp/server.mjs.map +1 -0
- package/dist/mcp/tools.cjs +218 -0
- package/dist/mcp/tools.d.cts +8 -0
- package/dist/mcp/tools.d.cts.map +1 -0
- package/dist/mcp/tools.d.mts +8 -0
- package/dist/mcp/tools.d.mts.map +1 -0
- package/dist/mcp/tools.mjs +219 -0
- package/dist/mcp/tools.mjs.map +1 -0
- package/dist/mcp/utils.cjs +75 -0
- package/dist/mcp/utils.mjs +69 -0
- package/dist/mcp/utils.mjs.map +1 -0
- package/dist/mount/index.cjs +4 -0
- package/dist/mount/index.d.cts +3 -0
- package/dist/mount/index.d.mts +3 -0
- package/dist/mount/index.mjs +3 -0
- package/dist/mount/materializer.cjs +167 -0
- package/dist/mount/materializer.d.cts +97 -0
- package/dist/mount/materializer.d.cts.map +1 -0
- package/dist/mount/materializer.d.mts +97 -0
- package/dist/mount/materializer.d.mts.map +1 -0
- package/dist/mount/materializer.mjs +167 -0
- package/dist/mount/materializer.mjs.map +1 -0
- package/dist/mount/types.d.cts +90 -0
- package/dist/mount/types.d.cts.map +1 -0
- package/dist/mount/types.d.mts +90 -0
- package/dist/mount/types.d.mts.map +1 -0
- package/dist/mount-config.cjs +61 -0
- package/dist/mount-config.d.cts +19 -0
- package/dist/mount-config.d.cts.map +1 -0
- package/dist/mount-config.d.mts +19 -0
- package/dist/mount-config.d.mts.map +1 -0
- package/dist/mount-config.mjs +61 -0
- package/dist/mount-config.mjs.map +1 -0
- package/dist/projectors/aup-projector.cjs +190 -0
- package/dist/projectors/aup-projector.d.cts +59 -0
- package/dist/projectors/aup-projector.d.cts.map +1 -0
- package/dist/projectors/aup-projector.d.mts +59 -0
- package/dist/projectors/aup-projector.d.mts.map +1 -0
- package/dist/projectors/aup-projector.mjs +188 -0
- package/dist/projectors/aup-projector.mjs.map +1 -0
- package/dist/projectors/index.cjs +6 -0
- package/dist/projectors/index.d.cts +2 -0
- package/dist/projectors/index.d.mts +2 -0
- package/dist/projectors/index.mjs +3 -0
- package/dist/scaffold/afs.cjs +195 -0
- package/dist/scaffold/afs.d.cts +16 -0
- package/dist/scaffold/afs.d.cts.map +1 -0
- package/dist/scaffold/afs.d.mts +16 -0
- package/dist/scaffold/afs.d.mts.map +1 -0
- package/dist/scaffold/afs.mjs +195 -0
- package/dist/scaffold/afs.mjs.map +1 -0
- package/dist/scaffold/composition.cjs +92 -0
- package/dist/scaffold/composition.d.cts +10 -0
- package/dist/scaffold/composition.d.cts.map +1 -0
- package/dist/scaffold/composition.d.mts +10 -0
- package/dist/scaffold/composition.d.mts.map +1 -0
- package/dist/scaffold/composition.mjs +92 -0
- package/dist/scaffold/composition.mjs.map +1 -0
- package/dist/scaffold/dsl-error.cjs +21 -0
- package/dist/scaffold/dsl-error.d.cts +11 -0
- package/dist/scaffold/dsl-error.d.cts.map +1 -0
- package/dist/scaffold/dsl-error.d.mts +11 -0
- package/dist/scaffold/dsl-error.d.mts.map +1 -0
- package/dist/scaffold/dsl-error.mjs +20 -0
- package/dist/scaffold/dsl-error.mjs.map +1 -0
- package/dist/scaffold/index.d.mts +12 -0
- package/dist/scaffold/manifest-dsl.cjs +188 -0
- package/dist/scaffold/manifest-dsl.d.cts +8 -0
- package/dist/scaffold/manifest-dsl.d.cts.map +1 -0
- package/dist/scaffold/manifest-dsl.d.mts +8 -0
- package/dist/scaffold/manifest-dsl.d.mts.map +1 -0
- package/dist/scaffold/manifest-dsl.mjs +188 -0
- package/dist/scaffold/manifest-dsl.mjs.map +1 -0
- package/dist/scaffold/module-dsl.cjs +284 -0
- package/dist/scaffold/module-dsl.d.cts +8 -0
- package/dist/scaffold/module-dsl.d.cts.map +1 -0
- package/dist/scaffold/module-dsl.d.mts +8 -0
- package/dist/scaffold/module-dsl.d.mts.map +1 -0
- package/dist/scaffold/module-dsl.mjs +284 -0
- package/dist/scaffold/module-dsl.mjs.map +1 -0
- package/dist/scaffold/resolver.cjs +156 -0
- package/dist/scaffold/resolver.d.cts +7 -0
- package/dist/scaffold/resolver.d.cts.map +1 -0
- package/dist/scaffold/resolver.d.mts +7 -0
- package/dist/scaffold/resolver.d.mts.map +1 -0
- package/dist/scaffold/resolver.mjs +156 -0
- package/dist/scaffold/resolver.mjs.map +1 -0
- package/dist/scaffold/runtime.cjs +24 -0
- package/dist/scaffold/runtime.d.cts +19 -0
- package/dist/scaffold/runtime.d.cts.map +1 -0
- package/dist/scaffold/runtime.d.mts +19 -0
- package/dist/scaffold/runtime.d.mts.map +1 -0
- package/dist/scaffold/runtime.mjs +25 -0
- package/dist/scaffold/runtime.mjs.map +1 -0
- package/dist/scaffold/shell-dsl.cjs +279 -0
- package/dist/scaffold/shell-dsl.d.cts +8 -0
- package/dist/scaffold/shell-dsl.d.cts.map +1 -0
- package/dist/scaffold/shell-dsl.d.mts +8 -0
- package/dist/scaffold/shell-dsl.d.mts.map +1 -0
- package/dist/scaffold/shell-dsl.mjs +279 -0
- package/dist/scaffold/shell-dsl.mjs.map +1 -0
- package/dist/scaffold/surface-dsl.cjs +182 -0
- package/dist/scaffold/surface-dsl.d.cts +8 -0
- package/dist/scaffold/surface-dsl.d.cts.map +1 -0
- package/dist/scaffold/surface-dsl.d.mts +8 -0
- package/dist/scaffold/surface-dsl.d.mts.map +1 -0
- package/dist/scaffold/surface-dsl.mjs +182 -0
- package/dist/scaffold/surface-dsl.mjs.map +1 -0
- package/dist/scaffold/surfaces.cjs +84 -0
- package/dist/scaffold/surfaces.d.cts +23 -0
- package/dist/scaffold/surfaces.d.cts.map +1 -0
- package/dist/scaffold/surfaces.d.mts +24 -0
- package/dist/scaffold/surfaces.d.mts.map +1 -0
- package/dist/scaffold/surfaces.mjs +84 -0
- package/dist/scaffold/surfaces.mjs.map +1 -0
- package/dist/scaffold/types.d.cts +264 -0
- package/dist/scaffold/types.d.cts.map +1 -0
- package/dist/scaffold/types.d.mts +265 -0
- package/dist/scaffold/types.d.mts.map +1 -0
- package/dist/scaffold/workspace-dsl.cjs +339 -0
- package/dist/scaffold/workspace-dsl.d.cts +49 -0
- package/dist/scaffold/workspace-dsl.d.cts.map +1 -0
- package/dist/scaffold/workspace-dsl.d.mts +49 -0
- package/dist/scaffold/workspace-dsl.d.mts.map +1 -0
- package/dist/scaffold/workspace-dsl.mjs +337 -0
- package/dist/scaffold/workspace-dsl.mjs.map +1 -0
- package/dist/scope/index.d.mts +3 -0
- package/dist/scope/read-blocklet-scope.cjs +29 -0
- package/dist/scope/read-blocklet-scope.d.cts +8 -0
- package/dist/scope/read-blocklet-scope.d.cts.map +1 -0
- package/dist/scope/read-blocklet-scope.d.mts +8 -0
- package/dist/scope/read-blocklet-scope.d.mts.map +1 -0
- package/dist/scope/read-blocklet-scope.mjs +30 -0
- package/dist/scope/read-blocklet-scope.mjs.map +1 -0
- package/dist/scope/resolve-scoped-afs.cjs +87 -0
- package/dist/scope/resolve-scoped-afs.d.cts +7 -0
- package/dist/scope/resolve-scoped-afs.d.cts.map +1 -0
- package/dist/scope/resolve-scoped-afs.d.mts +7 -0
- package/dist/scope/resolve-scoped-afs.d.mts.map +1 -0
- package/dist/scope/resolve-scoped-afs.mjs +88 -0
- package/dist/scope/resolve-scoped-afs.mjs.map +1 -0
- package/dist/scope/types.d.cts +25 -0
- package/dist/scope/types.d.cts.map +1 -0
- package/dist/scope/types.d.mts +25 -0
- package/dist/scope/types.d.mts.map +1 -0
- package/dist/session/backends.cjs +90 -0
- package/dist/session/backends.d.cts +35 -0
- package/dist/session/backends.d.cts.map +1 -0
- package/dist/session/backends.d.mts +35 -0
- package/dist/session/backends.d.mts.map +1 -0
- package/dist/session/backends.mjs +89 -0
- package/dist/session/backends.mjs.map +1 -0
- package/dist/session/replication-resolver.cjs +323 -0
- package/dist/session/replication-resolver.d.cts +71 -0
- package/dist/session/replication-resolver.d.cts.map +1 -0
- package/dist/session/replication-resolver.d.mts +71 -0
- package/dist/session/replication-resolver.d.mts.map +1 -0
- package/dist/session/replication-resolver.mjs +323 -0
- package/dist/session/replication-resolver.mjs.map +1 -0
- package/dist/session/role-level.cjs +36 -0
- package/dist/session/role-level.d.cts +24 -0
- package/dist/session/role-level.d.cts.map +1 -0
- package/dist/session/role-level.d.mts +24 -0
- package/dist/session/role-level.d.mts.map +1 -0
- package/dist/session/role-level.mjs +35 -0
- package/dist/session/role-level.mjs.map +1 -0
- package/dist/session/session-dir-providers.cjs +254 -0
- package/dist/session/session-dir-providers.d.cts +32 -0
- package/dist/session/session-dir-providers.d.cts.map +1 -0
- package/dist/session/session-dir-providers.d.mts +32 -0
- package/dist/session/session-dir-providers.d.mts.map +1 -0
- package/dist/session/session-dir-providers.mjs +252 -0
- package/dist/session/session-dir-providers.mjs.map +1 -0
- package/dist/session/session-id.cjs +20 -0
- package/dist/session/session-id.d.cts +15 -0
- package/dist/session/session-id.d.cts.map +1 -0
- package/dist/session/session-id.d.mts +15 -0
- package/dist/session/session-id.d.mts.map +1 -0
- package/dist/session/session-id.mjs +20 -0
- package/dist/session/session-id.mjs.map +1 -0
- package/dist/session/session-user-afs.cjs +899 -0
- package/dist/session/session-user-afs.d.cts +296 -0
- package/dist/session/session-user-afs.d.cts.map +1 -0
- package/dist/session/session-user-afs.d.mts +296 -0
- package/dist/session/session-user-afs.d.mts.map +1 -0
- package/dist/session/session-user-afs.mjs +896 -0
- package/dist/session/session-user-afs.mjs.map +1 -0
- package/dist/session/settings-cascade.cjs +69 -0
- package/dist/session/settings-cascade.d.cts +81 -0
- package/dist/session/settings-cascade.d.cts.map +1 -0
- package/dist/session/settings-cascade.d.mts +81 -0
- package/dist/session/settings-cascade.d.mts.map +1 -0
- package/dist/session/settings-cascade.mjs +69 -0
- package/dist/session/settings-cascade.mjs.map +1 -0
- package/dist/session/settings-resolver.cjs +770 -0
- package/dist/session/settings-resolver.d.cts +177 -0
- package/dist/session/settings-resolver.d.cts.map +1 -0
- package/dist/session/settings-resolver.d.mts +177 -0
- package/dist/session/settings-resolver.d.mts.map +1 -0
- package/dist/session/settings-resolver.mjs +771 -0
- package/dist/session/settings-resolver.mjs.map +1 -0
- package/dist/session/settings-schema-store.cjs +0 -0
- package/dist/session/settings-schema-store.d.cts +72 -0
- package/dist/session/settings-schema-store.d.cts.map +1 -0
- package/dist/session/settings-schema-store.d.mts +72 -0
- package/dist/session/settings-schema-store.d.mts.map +1 -0
- package/dist/session/settings-schema-store.mjs +0 -0
- package/dist/session/settings-schema-store.mjs.map +1 -0
- package/dist/system-mounts/index.cjs +14 -0
- package/dist/system-mounts/index.d.cts +6 -0
- package/dist/system-mounts/index.d.mts +6 -0
- package/dist/system-mounts/index.mjs +6 -0
- package/dist/system-mounts/install.cjs +101 -0
- package/dist/system-mounts/install.d.cts +36 -0
- package/dist/system-mounts/install.d.cts.map +1 -0
- package/dist/system-mounts/install.d.mts +37 -0
- package/dist/system-mounts/install.d.mts.map +1 -0
- package/dist/system-mounts/install.mjs +102 -0
- package/dist/system-mounts/install.mjs.map +1 -0
- package/dist/system-mounts/installers.cjs +247 -0
- package/dist/system-mounts/installers.d.cts +13 -0
- package/dist/system-mounts/installers.d.cts.map +1 -0
- package/dist/system-mounts/installers.d.mts +14 -0
- package/dist/system-mounts/installers.d.mts.map +1 -0
- package/dist/system-mounts/installers.mjs +244 -0
- package/dist/system-mounts/installers.mjs.map +1 -0
- package/dist/system-mounts/lists.cjs +122 -0
- package/dist/system-mounts/lists.d.cts +34 -0
- package/dist/system-mounts/lists.d.cts.map +1 -0
- package/dist/system-mounts/lists.d.mts +34 -0
- package/dist/system-mounts/lists.d.mts.map +1 -0
- package/dist/system-mounts/lists.mjs +122 -0
- package/dist/system-mounts/lists.mjs.map +1 -0
- package/dist/system-mounts/types.d.cts +124 -0
- package/dist/system-mounts/types.d.cts.map +1 -0
- package/dist/system-mounts/types.d.mts +125 -0
- package/dist/system-mounts/types.d.mts.map +1 -0
- package/dist/system-mounts/validate.cjs +56 -0
- package/dist/system-mounts/validate.d.cts +34 -0
- package/dist/system-mounts/validate.d.cts.map +1 -0
- package/dist/system-mounts/validate.d.mts +34 -0
- package/dist/system-mounts/validate.d.mts.map +1 -0
- package/dist/system-mounts/validate.mjs +56 -0
- package/dist/system-mounts/validate.mjs.map +1 -0
- package/dist/terminal-llm.cjs +90 -0
- package/dist/terminal-llm.d.cts +50 -0
- package/dist/terminal-llm.d.cts.map +1 -0
- package/dist/terminal-llm.d.mts +50 -0
- package/dist/terminal-llm.d.mts.map +1 -0
- package/dist/terminal-llm.mjs +90 -0
- package/dist/terminal-llm.mjs.map +1 -0
- package/dist/terminal-repl.cjs +371 -0
- package/dist/terminal-repl.d.cts +69 -0
- package/dist/terminal-repl.d.cts.map +1 -0
- package/dist/terminal-repl.d.mts +69 -0
- package/dist/terminal-repl.d.mts.map +1 -0
- package/dist/terminal-repl.mjs +370 -0
- package/dist/terminal-repl.mjs.map +1 -0
- package/dist/transport/idle-timer.cjs +136 -0
- package/dist/transport/idle-timer.d.cts +92 -0
- package/dist/transport/idle-timer.d.cts.map +1 -0
- package/dist/transport/idle-timer.d.mts +92 -0
- package/dist/transport/idle-timer.d.mts.map +1 -0
- package/dist/transport/idle-timer.mjs +132 -0
- package/dist/transport/idle-timer.mjs.map +1 -0
- package/dist/web-component-dsl.cjs +153 -0
- package/dist/web-component-dsl.d.cts +27 -0
- package/dist/web-component-dsl.d.cts.map +1 -0
- package/dist/web-component-dsl.d.mts +27 -0
- package/dist/web-component-dsl.d.mts.map +1 -0
- package/dist/web-component-dsl.mjs +152 -0
- package/dist/web-component-dsl.mjs.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { roleLevel } from "./role-level.mjs";
|
|
2
|
+
import { AFSForbiddenError } from "@aigne/afs";
|
|
3
|
+
|
|
4
|
+
//#region src/session/replication-resolver.ts
|
|
5
|
+
/**
|
|
6
|
+
* Compute the overlay mount prefix for a set of replicated collections.
|
|
7
|
+
* Returns the LCP (longest common prefix) of the "pre-collection" segments
|
|
8
|
+
* (everything before the collection-name segment, which sits just before `*`)
|
|
9
|
+
* across all canonical patterns. This is the path at which a single
|
|
10
|
+
* ReplicationResolver overlay is mounted in SessionUserAFS.
|
|
11
|
+
*
|
|
12
|
+
* Examples:
|
|
13
|
+
* { notes: { canonical: "instance/notes/*" } }
|
|
14
|
+
* → "instance" (pre-collection = ["instance"])
|
|
15
|
+
* { posts: "instance/app/posts/*", comments: "instance/app/comments/*" }
|
|
16
|
+
* → "instance/app" (LCP of ["instance","app"] and ["instance","app"])
|
|
17
|
+
* { notes: "instance/notes/*", posts: "instance/app/posts/*" }
|
|
18
|
+
* → "instance" (LCP of ["instance"] and ["instance","app"])
|
|
19
|
+
*/
|
|
20
|
+
function computeReplicationMountPrefix(collections) {
|
|
21
|
+
const entries = Object.values(collections);
|
|
22
|
+
if (entries.length === 0) throw new Error("No replicated collections declared");
|
|
23
|
+
const parents = entries.map(({ canonical }) => {
|
|
24
|
+
const segs = canonical.split("/").filter((s) => s.length > 0);
|
|
25
|
+
const starIdx = segs.indexOf("*");
|
|
26
|
+
if (starIdx < 1) throw new Error(`Invalid canonical pattern (no * or too short): ${canonical}`);
|
|
27
|
+
return segs.slice(0, starIdx - 1);
|
|
28
|
+
});
|
|
29
|
+
const first = parents[0];
|
|
30
|
+
let lcpLen = first.length;
|
|
31
|
+
for (let k = 1; k < parents.length; k++) {
|
|
32
|
+
const p = parents[k];
|
|
33
|
+
let len = 0;
|
|
34
|
+
while (len < lcpLen && len < p.length && first[len] === p[len]) len++;
|
|
35
|
+
lcpLen = len;
|
|
36
|
+
}
|
|
37
|
+
return first.slice(0, lcpLen).join("/") || "instance";
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Derive the sidecar path for a given item path, using the pattern to determine
|
|
41
|
+
* the injection point. Injects `.provenance/` before the collection segment
|
|
42
|
+
* (segment at firstStarIndex - 1 of the pattern).
|
|
43
|
+
*
|
|
44
|
+
* Examples:
|
|
45
|
+
* pattern="instance/app/posts/*", item="/instance/app/posts/p1"
|
|
46
|
+
* → "/instance/app/.provenance/posts/p1"
|
|
47
|
+
* pattern="instance/app/posts/{star}/comments/{star}", item="/instance/app/posts/p1/comments/c1"
|
|
48
|
+
* → "/instance/app/.provenance/posts/p1/comments/c1"
|
|
49
|
+
* Works for copy paths too: item="/user/app/posts/p1"
|
|
50
|
+
* → "/user/app/.provenance/posts/p1"
|
|
51
|
+
*/
|
|
52
|
+
function deriveSidecarPath(pattern, fullItemPath) {
|
|
53
|
+
const patternSegs = pattern.split("/").filter((s) => s.length > 0);
|
|
54
|
+
const firstStarIdx = patternSegs.indexOf("*");
|
|
55
|
+
if (firstStarIdx < 1) throw new Error(`Invalid pattern (no * or too short): ${pattern}`);
|
|
56
|
+
const preCollectionLen = firstStarIdx - 1;
|
|
57
|
+
const collectionSeg = patternSegs[firstStarIdx - 1];
|
|
58
|
+
const itemSegs = fullItemPath.replace(/^\//, "").split("/").filter((s) => s.length > 0);
|
|
59
|
+
const itemTail = itemSegs.slice(firstStarIdx);
|
|
60
|
+
return `/${[
|
|
61
|
+
...itemSegs.slice(0, preCollectionLen),
|
|
62
|
+
".provenance",
|
|
63
|
+
collectionSeg,
|
|
64
|
+
...itemTail
|
|
65
|
+
].join("/")}`;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Find the best-matching collection for a given (normalized, no-leading-slash) item path.
|
|
69
|
+
* Uses longest-match: the pattern that consumes the most path segments with static segments wins.
|
|
70
|
+
* Returns null if no collection matches.
|
|
71
|
+
*/
|
|
72
|
+
function matchCollections(collections, normalizedPath) {
|
|
73
|
+
const itemSegs = normalizedPath.replace(/^\//, "").split("/").filter((s) => s.length > 0);
|
|
74
|
+
let best = null;
|
|
75
|
+
for (const [name, collection] of Object.entries(collections)) {
|
|
76
|
+
const score = tryMatchPattern(collection.canonical.split("/").filter((s) => s.length > 0), itemSegs);
|
|
77
|
+
if (score !== null && (best === null || score > best.score)) best = {
|
|
78
|
+
name,
|
|
79
|
+
collection,
|
|
80
|
+
score
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return best ? {
|
|
84
|
+
name: best.name,
|
|
85
|
+
collection: best.collection
|
|
86
|
+
} : null;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Try to match a pattern (segment array) against an item path (segment array).
|
|
90
|
+
* Returns a score (higher = more specific) on match, or null on mismatch.
|
|
91
|
+
* Rules:
|
|
92
|
+
* - Static segments must match exactly.
|
|
93
|
+
* - Non-terminal `*` matches exactly one segment.
|
|
94
|
+
* - Terminal `*` matches one or more remaining segments (item + subtree).
|
|
95
|
+
*/
|
|
96
|
+
function tryMatchPattern(patternSegs, itemSegs) {
|
|
97
|
+
let pIdx = 0;
|
|
98
|
+
let iIdx = 0;
|
|
99
|
+
let score = 0;
|
|
100
|
+
while (pIdx < patternSegs.length) {
|
|
101
|
+
const pSeg = patternSegs[pIdx];
|
|
102
|
+
if (pSeg === "*") {
|
|
103
|
+
if (pIdx === patternSegs.length - 1) {
|
|
104
|
+
if (iIdx >= itemSegs.length) return null;
|
|
105
|
+
score += 10 + (itemSegs.length - iIdx - 1);
|
|
106
|
+
return score;
|
|
107
|
+
}
|
|
108
|
+
if (iIdx >= itemSegs.length) return null;
|
|
109
|
+
iIdx++;
|
|
110
|
+
score += 100;
|
|
111
|
+
} else {
|
|
112
|
+
if (iIdx >= itemSegs.length || itemSegs[iIdx] !== pSeg) return null;
|
|
113
|
+
iIdx++;
|
|
114
|
+
score += 1e3;
|
|
115
|
+
}
|
|
116
|
+
pIdx++;
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Match a path against canonical collection patterns OR their derived sidecar
|
|
122
|
+
* mirror patterns (`.provenance/` prefixed). Used for the read gate (P2).
|
|
123
|
+
*
|
|
124
|
+
* Sidecar matching: strips the `.provenance` segment from the path and checks
|
|
125
|
+
* the resulting path against the canonical patterns.
|
|
126
|
+
*/
|
|
127
|
+
function matchCollectionsForRead(collections, normalizedPath) {
|
|
128
|
+
const direct = matchCollections(collections, normalizedPath);
|
|
129
|
+
if (direct) return direct;
|
|
130
|
+
const segs = normalizedPath.split("/").filter((s) => s.length > 0);
|
|
131
|
+
const provenanceIdx = segs.indexOf(".provenance");
|
|
132
|
+
if (provenanceIdx < 0) return null;
|
|
133
|
+
return matchCollections(collections, [...segs.slice(0, provenanceIdx), ...segs.slice(provenanceIdx + 1)].join("/"));
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Check that a path contains no `..` or empty segments (path safety).
|
|
137
|
+
* Returns the normalized path (no leading `/`) on success.
|
|
138
|
+
* Throws AFSForbiddenError on suspicious input.
|
|
139
|
+
*/
|
|
140
|
+
function assertPathSafe(path) {
|
|
141
|
+
const norm = path.replace(/^\//, "");
|
|
142
|
+
const segs = norm.split("/");
|
|
143
|
+
for (const seg of segs) if (seg === "" || seg === "..") throw new AFSForbiddenError(path, `path contains unsafe segment: "${seg === "" ? "(empty)" : seg}"`);
|
|
144
|
+
return norm;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Convert a canonical `/instance/...` path to the author-copy `/user/...` path.
|
|
148
|
+
* Replaces the first segment (`instance`) with `user`.
|
|
149
|
+
* Returns the VIEW path (prefixed with /user) — callers must strip /user before
|
|
150
|
+
* passing to userBackend, since userBackend operates at provider-relative paths.
|
|
151
|
+
*/
|
|
152
|
+
function canonicalToViewCopyPath(canonicalPath) {
|
|
153
|
+
const norm = canonicalPath.replace(/^\//, "");
|
|
154
|
+
const slashIdx = norm.indexOf("/");
|
|
155
|
+
if (slashIdx < 0) return "/user";
|
|
156
|
+
return `/user${norm.slice(slashIdx)}`;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Strip the `/user` view prefix from a copy view path to get the
|
|
160
|
+
* provider-relative path for userBackend calls.
|
|
161
|
+
*/
|
|
162
|
+
function copyViewToProviderPath(viewPath) {
|
|
163
|
+
if (viewPath === "/user") return "/";
|
|
164
|
+
if (viewPath.startsWith("/user/")) return viewPath.slice(5);
|
|
165
|
+
return viewPath;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* ReplicationResolver intercepts writes and deletes to replicated-collection
|
|
169
|
+
* paths, enforces role gates, and fans out to both canonical (instanceBackend)
|
|
170
|
+
* and author copy (userBackend) with provenance sidecars.
|
|
171
|
+
*
|
|
172
|
+
* Mounted as a SessionUserAFS overlay at `/${mountPrefix}` (typically
|
|
173
|
+
* /instance). Receives SUB-PATHS (after prefix stripping by the overlay
|
|
174
|
+
* system) and reconstructs full paths internally using mountPrefix.
|
|
175
|
+
*
|
|
176
|
+
* P1: write + delete implemented.
|
|
177
|
+
* P2: read gate (readRole) + createSessionView wiring.
|
|
178
|
+
*/
|
|
179
|
+
var ReplicationResolver = class {
|
|
180
|
+
name;
|
|
181
|
+
mountPrefix;
|
|
182
|
+
constructor(opts) {
|
|
183
|
+
this.opts = opts;
|
|
184
|
+
this.name = `replication-resolver:${opts.sessionId}`;
|
|
185
|
+
this.mountPrefix = opts.mountPrefix ?? computeReplicationMountPrefix(opts.collections);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Reconstruct the full canonical path from the sub-path the overlay delivers.
|
|
189
|
+
* The overlay at `/${mountPrefix}` strips the prefix, leaving e.g. `/notes/n1`
|
|
190
|
+
* from `/instance/notes/n1`. We prepend mountPrefix to restore the full path.
|
|
191
|
+
*/
|
|
192
|
+
toFullPath(sub) {
|
|
193
|
+
const s = sub.startsWith("/") ? sub : `/${sub}`;
|
|
194
|
+
return `/${this.mountPrefix}${s}`;
|
|
195
|
+
}
|
|
196
|
+
async getEffectiveMinRole(collectionName, collection) {
|
|
197
|
+
if (this.opts.settingsResolver) try {
|
|
198
|
+
const v = (await this.opts.settingsResolver.read(`/${collectionName}/minRole.json/value`)).data?.content;
|
|
199
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
200
|
+
} catch {}
|
|
201
|
+
return collection.minRole ?? "member";
|
|
202
|
+
}
|
|
203
|
+
async getEffectiveReadRole(collectionName, collection) {
|
|
204
|
+
if (this.opts.settingsResolver) try {
|
|
205
|
+
const v = (await this.opts.settingsResolver.read(`/${collectionName}/readRole.json/value`)).data?.content;
|
|
206
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
207
|
+
} catch {}
|
|
208
|
+
return collection.readRole ?? "guest";
|
|
209
|
+
}
|
|
210
|
+
async read(path, options) {
|
|
211
|
+
const fullPath = this.toFullPath(path);
|
|
212
|
+
const normPath = assertPathSafe(fullPath);
|
|
213
|
+
const match = matchCollectionsForRead(this.opts.collections, normPath);
|
|
214
|
+
if (match) {
|
|
215
|
+
const effectiveReadRole = await this.getEffectiveReadRole(match.name, match.collection);
|
|
216
|
+
if (roleLevel(this.opts.callerRole) < roleLevel(effectiveReadRole)) throw new AFSForbiddenError(fullPath, `role '${this.opts.callerRole ?? "guest"}' is below readRole '${effectiveReadRole}'`);
|
|
217
|
+
}
|
|
218
|
+
if (!this.opts.instanceBackend.read) throw new AFSForbiddenError(fullPath, "instanceBackend does not support read");
|
|
219
|
+
return this.opts.instanceBackend.read(fullPath, options);
|
|
220
|
+
}
|
|
221
|
+
async list(path, options) {
|
|
222
|
+
const fullPath = this.toFullPath(path);
|
|
223
|
+
const normPath = assertPathSafe(fullPath);
|
|
224
|
+
const match = matchCollectionsForRead(this.opts.collections, normPath);
|
|
225
|
+
if (match) {
|
|
226
|
+
const effectiveReadRole = await this.getEffectiveReadRole(match.name, match.collection);
|
|
227
|
+
if (roleLevel(this.opts.callerRole) < roleLevel(effectiveReadRole)) throw new AFSForbiddenError(fullPath, `role '${this.opts.callerRole ?? "guest"}' is below readRole '${effectiveReadRole}'`);
|
|
228
|
+
}
|
|
229
|
+
if (!this.opts.instanceBackend.list) throw new AFSForbiddenError(fullPath, "instanceBackend does not support list");
|
|
230
|
+
return this.opts.instanceBackend.list(fullPath, options);
|
|
231
|
+
}
|
|
232
|
+
async stat(path, options) {
|
|
233
|
+
const fullPath = this.toFullPath(path);
|
|
234
|
+
const normPath = assertPathSafe(fullPath);
|
|
235
|
+
const match = matchCollectionsForRead(this.opts.collections, normPath);
|
|
236
|
+
if (match) {
|
|
237
|
+
const effectiveReadRole = await this.getEffectiveReadRole(match.name, match.collection);
|
|
238
|
+
if (roleLevel(this.opts.callerRole) < roleLevel(effectiveReadRole)) throw new AFSForbiddenError(fullPath, `role '${this.opts.callerRole ?? "guest"}' is below readRole '${effectiveReadRole}'`);
|
|
239
|
+
}
|
|
240
|
+
if (!this.opts.instanceBackend.stat) throw new AFSForbiddenError(fullPath, "instanceBackend does not support stat");
|
|
241
|
+
return this.opts.instanceBackend.stat(fullPath, options);
|
|
242
|
+
}
|
|
243
|
+
async search(path, query, options) {
|
|
244
|
+
const fullPath = this.toFullPath(path);
|
|
245
|
+
const normPath = assertPathSafe(fullPath);
|
|
246
|
+
const match = matchCollectionsForRead(this.opts.collections, normPath);
|
|
247
|
+
if (match) {
|
|
248
|
+
const effectiveReadRole = await this.getEffectiveReadRole(match.name, match.collection);
|
|
249
|
+
if (roleLevel(this.opts.callerRole) < roleLevel(effectiveReadRole)) throw new AFSForbiddenError(fullPath, `role '${this.opts.callerRole ?? "guest"}' is below readRole '${effectiveReadRole}'`);
|
|
250
|
+
}
|
|
251
|
+
if (!this.opts.instanceBackend.search) throw new AFSForbiddenError(fullPath, "instanceBackend does not support search");
|
|
252
|
+
return this.opts.instanceBackend.search(fullPath, query, options);
|
|
253
|
+
}
|
|
254
|
+
async write(path, content, options) {
|
|
255
|
+
const fullPath = this.toFullPath(path);
|
|
256
|
+
const normPath = assertPathSafe(fullPath);
|
|
257
|
+
const match = matchCollections(this.opts.collections, normPath);
|
|
258
|
+
if (!match) {
|
|
259
|
+
if ((options?.context)?.callerOrigin === "network") throw new AFSForbiddenError(fullPath, "network clients cannot write non-replicated instance paths");
|
|
260
|
+
if (!this.opts.instanceBackend.write) throw new AFSForbiddenError(fullPath, "instanceBackend does not support write");
|
|
261
|
+
return this.opts.instanceBackend.write(fullPath, content, options);
|
|
262
|
+
}
|
|
263
|
+
const { name: collectionName, collection } = match;
|
|
264
|
+
const effectiveMinRole = await this.getEffectiveMinRole(collectionName, collection);
|
|
265
|
+
if (roleLevel(this.opts.callerRole) < roleLevel(effectiveMinRole)) throw new AFSForbiddenError(fullPath, `role '${this.opts.callerRole ?? "guest"}' is below minRole '${effectiveMinRole}'`);
|
|
266
|
+
const authorDid = this.opts.callerDid ?? null;
|
|
267
|
+
const publishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
268
|
+
if ((collection.copy ?? "mirror") !== "none" && this.opts.userBackend !== null && this.opts.userBackend.write) {
|
|
269
|
+
const copyViewPath = canonicalToViewCopyPath(fullPath);
|
|
270
|
+
const copyProviderPath = copyViewToProviderPath(copyViewPath);
|
|
271
|
+
await this.opts.userBackend.write(copyProviderPath, content);
|
|
272
|
+
const copySidecarProviderPath = copyViewToProviderPath(deriveSidecarPath(collection.canonical, copyViewPath));
|
|
273
|
+
const copySidecar = {
|
|
274
|
+
canonical_ref: {
|
|
275
|
+
instance_did: this.opts.instanceDid ?? "",
|
|
276
|
+
path: normPath.replace(/^instance\//, "")
|
|
277
|
+
},
|
|
278
|
+
published_at: publishedAt
|
|
279
|
+
};
|
|
280
|
+
await this.opts.userBackend.write(copySidecarProviderPath, { content: JSON.stringify(copySidecar) });
|
|
281
|
+
}
|
|
282
|
+
if (!this.opts.instanceBackend.write) throw new AFSForbiddenError(fullPath, "instanceBackend does not support write");
|
|
283
|
+
const result = await this.opts.instanceBackend.write(fullPath, content, options);
|
|
284
|
+
const canonicalSidecarPath = deriveSidecarPath(collection.canonical, fullPath);
|
|
285
|
+
const canonicalSidecar = {
|
|
286
|
+
author_did: authorDid,
|
|
287
|
+
published_at: publishedAt
|
|
288
|
+
};
|
|
289
|
+
await this.opts.instanceBackend.write(canonicalSidecarPath, { content: JSON.stringify(canonicalSidecar) });
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
292
|
+
async delete(path, options) {
|
|
293
|
+
const fullPath = this.toFullPath(path);
|
|
294
|
+
const normPath = assertPathSafe(fullPath);
|
|
295
|
+
const match = matchCollections(this.opts.collections, normPath);
|
|
296
|
+
if (!match) {
|
|
297
|
+
if (!this.opts.instanceBackend.delete) return {};
|
|
298
|
+
return this.opts.instanceBackend.delete(fullPath, options);
|
|
299
|
+
}
|
|
300
|
+
const { collection } = match;
|
|
301
|
+
const canonicalSidecarPath = deriveSidecarPath(collection.canonical, fullPath);
|
|
302
|
+
let provenanceAuthorDid = null;
|
|
303
|
+
if (this.opts.instanceBackend.read) try {
|
|
304
|
+
const raw = (await this.opts.instanceBackend.read(canonicalSidecarPath)).data?.content;
|
|
305
|
+
if (typeof raw === "string") provenanceAuthorDid = JSON.parse(raw).author_did ?? null;
|
|
306
|
+
} catch {}
|
|
307
|
+
const callerDid = this.opts.callerDid;
|
|
308
|
+
const anyItemRoleStr = collection.delete?.anyItem ?? "admin";
|
|
309
|
+
const isOwnItem = callerDid != null && provenanceAuthorDid != null && callerDid === provenanceAuthorDid;
|
|
310
|
+
const hasAnyItemRole = roleLevel(this.opts.callerRole) >= roleLevel(anyItemRoleStr);
|
|
311
|
+
if (!isOwnItem && !hasAnyItemRole) throw new AFSForbiddenError(fullPath, `insufficient permission to delete: role='${this.opts.callerRole ?? "guest"}'`);
|
|
312
|
+
if (!this.opts.instanceBackend.delete) return {};
|
|
313
|
+
const result = await this.opts.instanceBackend.delete(fullPath, options);
|
|
314
|
+
try {
|
|
315
|
+
await this.opts.instanceBackend.delete(canonicalSidecarPath);
|
|
316
|
+
} catch {}
|
|
317
|
+
return result;
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
//#endregion
|
|
322
|
+
export { ReplicationResolver, computeReplicationMountPrefix };
|
|
323
|
+
//# sourceMappingURL=replication-resolver.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replication-resolver.mjs","names":[],"sources":["../../src/session/replication-resolver.ts"],"sourcesContent":["/**\n * ReplicationResolver — per-session replicated-collection fan-out gate.\n *\n * P1: write fan-out (role gate + copy + canonical + provenance sidecar) +\n * delete gate (ownItem/anyItem + no cascade).\n * P2: read gate (readRole) + createSessionView wiring.\n *\n * See planning/did-integration/data-ownership-models/tasks.md §Phase 1/2.\n */\n\nimport type {\n AFSContext,\n AFSDeleteOptions,\n AFSDeleteResult,\n AFSListOptions,\n AFSListResult,\n AFSModule,\n AFSReadOptions,\n AFSReadResult,\n AFSRoot,\n AFSSearchOptions,\n AFSSearchResult,\n AFSStatOptions,\n AFSStatResult,\n AFSWriteEntryPayload,\n AFSWriteOptions,\n AFSWriteResult,\n ReplicatedCollection,\n} from \"@aigne/afs\";\nimport { AFSForbiddenError } from \"@aigne/afs\";\nimport { roleLevel } from \"./role-level.js\";\nimport type { SettingsResolver } from \"./settings-resolver.js\";\n\n/**\n * Construction options for ReplicationResolver.\n * Injected at createSessionView (P2), not pre-built upstream.\n */\nexport interface ReplicationResolverOptions {\n /**\n * Parsed replicated-collection declarations from the blocklet manifest.\n * Key = logical collection name; value = validated ReplicatedCollection.\n */\n collections: Record<string, ReplicatedCollection>;\n /** The canonical backend (instance space, role:system). */\n instanceBackend: AFSRoot;\n /** The author-copy backend (user space, role:user), or null for anonymous callers. */\n userBackend: AFSModule | null;\n /** SettingsResolver reference for reading effective minRole/readRole overrides. */\n settingsResolver: SettingsResolver | null;\n /** Verified caller DID (from AFSContext.caller.did, never client-supplied). */\n callerDid: string | undefined;\n /** Caller role (daemon-folded single string from getCallerForSession). */\n callerRole: string | undefined;\n /** Owning session id — for event origin tagging in P2+. */\n sessionId: string;\n /** Instance DID used in copy-side sidecar canonical_ref. */\n instanceDid?: string;\n /**\n * The path prefix this resolver is mounted at, WITHOUT leading slash.\n * e.g. \"instance\" when mounted at /instance. Used to reconstruct the full\n * canonical path from the sub-path the overlay system delivers.\n * When omitted, computed automatically via computeReplicationMountPrefix().\n */\n mountPrefix?: string;\n}\n\n// ── Pure helper functions (exported for unit-testing) ──────────────────────\n\n/**\n * Compute the overlay mount prefix for a set of replicated collections.\n * Returns the LCP (longest common prefix) of the \"pre-collection\" segments\n * (everything before the collection-name segment, which sits just before `*`)\n * across all canonical patterns. This is the path at which a single\n * ReplicationResolver overlay is mounted in SessionUserAFS.\n *\n * Examples:\n * { notes: { canonical: \"instance/notes/*\" } }\n * → \"instance\" (pre-collection = [\"instance\"])\n * { posts: \"instance/app/posts/*\", comments: \"instance/app/comments/*\" }\n * → \"instance/app\" (LCP of [\"instance\",\"app\"] and [\"instance\",\"app\"])\n * { notes: \"instance/notes/*\", posts: \"instance/app/posts/*\" }\n * → \"instance\" (LCP of [\"instance\"] and [\"instance\",\"app\"])\n */\nexport function computeReplicationMountPrefix(\n collections: Record<string, ReplicatedCollection>,\n): string {\n const entries = Object.values(collections);\n if (entries.length === 0) throw new Error(\"No replicated collections declared\");\n\n const parents: string[][] = entries.map(({ canonical }) => {\n const segs = canonical.split(\"/\").filter((s) => s.length > 0);\n const starIdx = segs.indexOf(\"*\");\n if (starIdx < 1) throw new Error(`Invalid canonical pattern (no * or too short): ${canonical}`);\n // Everything before the collection segment (at starIdx - 1)\n return segs.slice(0, starIdx - 1);\n });\n\n const first = parents[0]!;\n let lcpLen = first.length;\n for (let k = 1; k < parents.length; k++) {\n const p = parents[k]!;\n let len = 0;\n while (len < lcpLen && len < p.length && first[len] === p[len]) len++;\n lcpLen = len;\n }\n\n // Must resolve to at least \"instance\"\n const prefix = first.slice(0, lcpLen).join(\"/\");\n return prefix || \"instance\";\n}\n\n/**\n * Derive the sidecar path for a given item path, using the pattern to determine\n * the injection point. Injects `.provenance/` before the collection segment\n * (segment at firstStarIndex - 1 of the pattern).\n *\n * Examples:\n * pattern=\"instance/app/posts/*\", item=\"/instance/app/posts/p1\"\n * → \"/instance/app/.provenance/posts/p1\"\n * pattern=\"instance/app/posts/{star}/comments/{star}\", item=\"/instance/app/posts/p1/comments/c1\"\n * → \"/instance/app/.provenance/posts/p1/comments/c1\"\n * Works for copy paths too: item=\"/user/app/posts/p1\"\n * → \"/user/app/.provenance/posts/p1\"\n */\nexport function deriveSidecarPath(pattern: string, fullItemPath: string): string {\n const patternSegs = pattern.split(\"/\").filter((s) => s.length > 0);\n const firstStarIdx = patternSegs.indexOf(\"*\");\n if (firstStarIdx < 1) throw new Error(`Invalid pattern (no * or too short): ${pattern}`);\n\n // segments before the first * = static prefix\n // e.g. [\"instance\",\"app\",\"posts\"] for \"instance/app/posts/*\"\n const preCollectionLen = firstStarIdx - 1; // count of segments before collection\n const collectionSeg = patternSegs[firstStarIdx - 1]; // e.g. \"posts\"\n\n const normPath = fullItemPath.replace(/^\\//, \"\");\n const itemSegs = normPath.split(\"/\").filter((s) => s.length > 0);\n\n // Everything from firstStarIdx onwards is the item tail\n const itemTail = itemSegs.slice(firstStarIdx);\n // Segments from the actual path before the collection (may be \"user\" or \"instance\")\n const preCollectionFromPath = itemSegs.slice(0, preCollectionLen);\n\n return `/${[...preCollectionFromPath, \".provenance\", collectionSeg, ...itemTail].join(\"/\")}`;\n}\n\n/**\n * Find the best-matching collection for a given (normalized, no-leading-slash) item path.\n * Uses longest-match: the pattern that consumes the most path segments with static segments wins.\n * Returns null if no collection matches.\n */\nexport function matchCollections(\n collections: Record<string, ReplicatedCollection>,\n normalizedPath: string,\n): { name: string; collection: ReplicatedCollection } | null {\n const itemSegs = normalizedPath\n .replace(/^\\//, \"\")\n .split(\"/\")\n .filter((s) => s.length > 0);\n\n let best: { name: string; collection: ReplicatedCollection; score: number } | null = null;\n\n for (const [name, collection] of Object.entries(collections)) {\n const patternSegs = collection.canonical.split(\"/\").filter((s) => s.length > 0);\n const score = tryMatchPattern(patternSegs, itemSegs);\n if (score !== null && (best === null || score > best.score)) {\n best = { name, collection, score };\n }\n }\n\n return best ? { name: best.name, collection: best.collection } : null;\n}\n\n/**\n * Try to match a pattern (segment array) against an item path (segment array).\n * Returns a score (higher = more specific) on match, or null on mismatch.\n * Rules:\n * - Static segments must match exactly.\n * - Non-terminal `*` matches exactly one segment.\n * - Terminal `*` matches one or more remaining segments (item + subtree).\n */\nfunction tryMatchPattern(patternSegs: string[], itemSegs: string[]): number | null {\n let pIdx = 0;\n let iIdx = 0;\n let score = 0;\n\n while (pIdx < patternSegs.length) {\n const pSeg = patternSegs[pIdx];\n\n if (pSeg === \"*\") {\n if (pIdx === patternSegs.length - 1) {\n // Terminal *: must have at least one remaining item segment\n if (iIdx >= itemSegs.length) return null;\n // Score: +100 per non-star segment already consumed, +10 for the star\n score += 10 + (itemSegs.length - iIdx - 1);\n return score;\n }\n // Non-terminal *: match exactly one item segment\n if (iIdx >= itemSegs.length) return null;\n iIdx++;\n score += 100;\n } else {\n // Static segment: exact match required\n if (iIdx >= itemSegs.length || itemSegs[iIdx] !== pSeg) return null;\n iIdx++;\n score += 1000;\n }\n pIdx++;\n }\n\n return null; // valid patterns always end with *, so this is unreachable\n}\n\n/**\n * Match a path against canonical collection patterns OR their derived sidecar\n * mirror patterns (`.provenance/` prefixed). Used for the read gate (P2).\n *\n * Sidecar matching: strips the `.provenance` segment from the path and checks\n * the resulting path against the canonical patterns.\n */\nfunction matchCollectionsForRead(\n collections: Record<string, ReplicatedCollection>,\n normalizedPath: string,\n): { name: string; collection: ReplicatedCollection } | null {\n // 1. Direct canonical match\n const direct = matchCollections(collections, normalizedPath);\n if (direct) return direct;\n\n // 2. Sidecar match: strip `.provenance` segment and retry\n const segs = normalizedPath.split(\"/\").filter((s) => s.length > 0);\n const provenanceIdx = segs.indexOf(\".provenance\");\n if (provenanceIdx < 0) return null;\n const withoutProvenance = [\n ...segs.slice(0, provenanceIdx),\n ...segs.slice(provenanceIdx + 1),\n ].join(\"/\");\n return matchCollections(collections, withoutProvenance);\n}\n\n/**\n * Check that a path contains no `..` or empty segments (path safety).\n * Returns the normalized path (no leading `/`) on success.\n * Throws AFSForbiddenError on suspicious input.\n */\nfunction assertPathSafe(path: string): string {\n const norm = path.replace(/^\\//, \"\");\n const segs = norm.split(\"/\");\n for (const seg of segs) {\n if (seg === \"\" || seg === \"..\") {\n throw new AFSForbiddenError(\n path,\n `path contains unsafe segment: \"${seg === \"\" ? \"(empty)\" : seg}\"`,\n );\n }\n }\n return norm;\n}\n\n/**\n * Convert a canonical `/instance/...` path to the author-copy `/user/...` path.\n * Replaces the first segment (`instance`) with `user`.\n * Returns the VIEW path (prefixed with /user) — callers must strip /user before\n * passing to userBackend, since userBackend operates at provider-relative paths.\n */\nfunction canonicalToViewCopyPath(canonicalPath: string): string {\n const norm = canonicalPath.replace(/^\\//, \"\");\n const slashIdx = norm.indexOf(\"/\");\n if (slashIdx < 0) return \"/user\";\n return `/user${norm.slice(slashIdx)}`;\n}\n\n/**\n * Strip the `/user` view prefix from a copy view path to get the\n * provider-relative path for userBackend calls.\n */\nfunction copyViewToProviderPath(viewPath: string): string {\n if (viewPath === \"/user\") return \"/\";\n if (viewPath.startsWith(\"/user/\")) return viewPath.slice(\"/user\".length);\n return viewPath;\n}\n\n// ── ReplicationResolver ────────────────────────────────────────────────────\n\n/**\n * ReplicationResolver intercepts writes and deletes to replicated-collection\n * paths, enforces role gates, and fans out to both canonical (instanceBackend)\n * and author copy (userBackend) with provenance sidecars.\n *\n * Mounted as a SessionUserAFS overlay at `/${mountPrefix}` (typically\n * /instance). Receives SUB-PATHS (after prefix stripping by the overlay\n * system) and reconstructs full paths internally using mountPrefix.\n *\n * P1: write + delete implemented.\n * P2: read gate (readRole) + createSessionView wiring.\n */\nexport class ReplicationResolver implements AFSModule {\n readonly name: string;\n private readonly mountPrefix: string;\n\n constructor(private readonly opts: ReplicationResolverOptions) {\n this.name = `replication-resolver:${opts.sessionId}`;\n this.mountPrefix = opts.mountPrefix ?? computeReplicationMountPrefix(opts.collections);\n }\n\n // ── Path helpers ──────────────────────────────────────────────────────────\n\n /**\n * Reconstruct the full canonical path from the sub-path the overlay delivers.\n * The overlay at `/${mountPrefix}` strips the prefix, leaving e.g. `/notes/n1`\n * from `/instance/notes/n1`. We prepend mountPrefix to restore the full path.\n */\n private toFullPath(sub: string): string {\n const s = sub.startsWith(\"/\") ? sub : `/${sub}`;\n return `/${this.mountPrefix}${s}`;\n }\n\n // ── Helpers ──────────────────────────────────────────────────────────────\n\n private async getEffectiveMinRole(\n collectionName: string,\n collection: ReplicatedCollection,\n ): Promise<string> {\n if (this.opts.settingsResolver) {\n try {\n const result = await this.opts.settingsResolver.read(\n `/${collectionName}/minRole.json/value`,\n );\n const v = result.data?.content;\n if (typeof v === \"string\" && v.length > 0) return v;\n } catch {\n // No operator override — fall through to manifest default\n }\n }\n return collection.minRole ?? \"member\";\n }\n\n private async getEffectiveReadRole(\n collectionName: string,\n collection: ReplicatedCollection,\n ): Promise<string> {\n if (this.opts.settingsResolver) {\n try {\n const result = await this.opts.settingsResolver.read(\n `/${collectionName}/readRole.json/value`,\n );\n const v = result.data?.content;\n if (typeof v === \"string\" && v.length > 0) return v;\n } catch {\n // No operator override — fall through to manifest default\n }\n }\n return collection.readRole ?? \"guest\";\n }\n\n // ── AFSModule.read ────────────────────────────────────────────────────────\n\n async read(path: string, options?: AFSReadOptions): Promise<AFSReadResult> {\n const fullPath = this.toFullPath(path);\n const normPath = assertPathSafe(fullPath);\n\n // Check if the path matches a replicated collection (canonical or sidecar)\n const match = matchCollectionsForRead(this.opts.collections, normPath);\n if (match) {\n const effectiveReadRole = await this.getEffectiveReadRole(match.name, match.collection);\n // guest (default) → roleLevel 0 ≤ any caller level → zero-overhead pass-through\n if (roleLevel(this.opts.callerRole) < roleLevel(effectiveReadRole)) {\n throw new AFSForbiddenError(\n fullPath,\n `role '${this.opts.callerRole ?? \"guest\"}' is below readRole '${effectiveReadRole}'`,\n );\n }\n }\n // Both matching (passed gate) and non-matching paths delegate to instanceBackend\n if (!this.opts.instanceBackend.read) {\n throw new AFSForbiddenError(fullPath, \"instanceBackend does not support read\");\n }\n return this.opts.instanceBackend.read(fullPath, options);\n }\n\n // ── AFSModule.list ────────────────────────────────────────────────────────\n\n async list(path: string, options?: AFSListOptions): Promise<AFSListResult> {\n const fullPath = this.toFullPath(path);\n const normPath = assertPathSafe(fullPath);\n\n const match = matchCollectionsForRead(this.opts.collections, normPath);\n if (match) {\n const effectiveReadRole = await this.getEffectiveReadRole(match.name, match.collection);\n if (roleLevel(this.opts.callerRole) < roleLevel(effectiveReadRole)) {\n throw new AFSForbiddenError(\n fullPath,\n `role '${this.opts.callerRole ?? \"guest\"}' is below readRole '${effectiveReadRole}'`,\n );\n }\n }\n if (!this.opts.instanceBackend.list) {\n throw new AFSForbiddenError(fullPath, \"instanceBackend does not support list\");\n }\n return this.opts.instanceBackend.list(fullPath, options);\n }\n\n // ── AFSModule.stat ────────────────────────────────────────────────────────\n\n async stat(path: string, options?: AFSStatOptions): Promise<AFSStatResult> {\n const fullPath = this.toFullPath(path);\n const normPath = assertPathSafe(fullPath);\n\n const match = matchCollectionsForRead(this.opts.collections, normPath);\n if (match) {\n const effectiveReadRole = await this.getEffectiveReadRole(match.name, match.collection);\n if (roleLevel(this.opts.callerRole) < roleLevel(effectiveReadRole)) {\n throw new AFSForbiddenError(\n fullPath,\n `role '${this.opts.callerRole ?? \"guest\"}' is below readRole '${effectiveReadRole}'`,\n );\n }\n }\n if (!this.opts.instanceBackend.stat) {\n throw new AFSForbiddenError(fullPath, \"instanceBackend does not support stat\");\n }\n return this.opts.instanceBackend.stat(fullPath, options);\n }\n\n // ── AFSModule.search ──────────────────────────────────────────────────────\n\n async search(path: string, query: string, options?: AFSSearchOptions): Promise<AFSSearchResult> {\n const fullPath = this.toFullPath(path);\n const normPath = assertPathSafe(fullPath);\n\n const match = matchCollectionsForRead(this.opts.collections, normPath);\n if (match) {\n const effectiveReadRole = await this.getEffectiveReadRole(match.name, match.collection);\n if (roleLevel(this.opts.callerRole) < roleLevel(effectiveReadRole)) {\n throw new AFSForbiddenError(\n fullPath,\n `role '${this.opts.callerRole ?? \"guest\"}' is below readRole '${effectiveReadRole}'`,\n );\n }\n }\n if (!this.opts.instanceBackend.search) {\n throw new AFSForbiddenError(fullPath, \"instanceBackend does not support search\");\n }\n return this.opts.instanceBackend.search(fullPath, query, options);\n }\n\n // ── AFSModule.write ───────────────────────────────────────────────────────\n\n async write(\n path: string,\n content: AFSWriteEntryPayload,\n options?: AFSWriteOptions,\n ): Promise<AFSWriteResult> {\n // 1. Reconstruct full path and path safety check\n const fullPath = this.toFullPath(path);\n const normPath = assertPathSafe(fullPath);\n\n // 2. Find matching collection (longest-match)\n const match = matchCollections(this.opts.collections, normPath);\n if (!match) {\n // Non-matching path: gate network clients (instance-write-authz §5.5 / L3).\n // Internal writes (seeding / agents / CLI) carry no callerOrigin → pass through.\n const ctx = options?.context as AFSContext | undefined;\n if (ctx?.callerOrigin === \"network\") {\n throw new AFSForbiddenError(\n fullPath,\n \"network clients cannot write non-replicated instance paths\",\n );\n }\n if (!this.opts.instanceBackend.write) {\n throw new AFSForbiddenError(fullPath, \"instanceBackend does not support write\");\n }\n return this.opts.instanceBackend.write(fullPath, content, options);\n }\n\n const { name: collectionName, collection } = match;\n\n // 3. Role gate\n const effectiveMinRole = await this.getEffectiveMinRole(collectionName, collection);\n if (roleLevel(this.opts.callerRole) < roleLevel(effectiveMinRole)) {\n throw new AFSForbiddenError(\n fullPath,\n `role '${this.opts.callerRole ?? \"guest\"}' is below minRole '${effectiveMinRole}'`,\n );\n }\n\n // 4. Provenance data (author_did is from verified caller, never client-supplied)\n const authorDid = this.opts.callerDid ?? null;\n const publishedAt = new Date().toISOString();\n\n const copyMode = collection.copy ?? \"mirror\";\n\n // 5. Step ③: write author copy (skip if copy:none or no userBackend)\n if (copyMode !== \"none\" && this.opts.userBackend !== null && this.opts.userBackend.write) {\n // View path: /user/notes/n1 (mirrors canonical /instance/notes/n1)\n const copyViewPath = canonicalToViewCopyPath(fullPath);\n // Provider-relative path (strip /user prefix for the userBackend provider)\n const copyProviderPath = copyViewToProviderPath(copyViewPath);\n\n // Author copy: unconditional (no ifMatch)\n await this.opts.userBackend.write(copyProviderPath, content);\n\n // Author-side sidecar (uses VIEW path for deriveSidecarPath, then strips /user)\n const copySidecarViewPath = deriveSidecarPath(collection.canonical, copyViewPath);\n const copySidecarProviderPath = copyViewToProviderPath(copySidecarViewPath);\n const copySidecar = {\n canonical_ref: {\n instance_did: this.opts.instanceDid ?? \"\",\n // Path relative to instance root (strip \"instance/\" prefix)\n path: normPath.replace(/^instance\\//, \"\"),\n },\n published_at: publishedAt,\n };\n await this.opts.userBackend.write(copySidecarProviderPath, {\n content: JSON.stringify(copySidecar),\n });\n }\n\n // 6. Step ④: write canonical (with caller's ifMatch if provided)\n if (!this.opts.instanceBackend.write) {\n throw new AFSForbiddenError(fullPath, \"instanceBackend does not support write\");\n }\n const result = await this.opts.instanceBackend.write(fullPath, content, options);\n\n // Canonical-side sidecar (after canonical write succeeds)\n const canonicalSidecarPath = deriveSidecarPath(collection.canonical, fullPath);\n const canonicalSidecar = {\n // copy:none + no callerDid (true anonymous) → null; otherwise use verified did\n author_did: authorDid,\n published_at: publishedAt,\n };\n await this.opts.instanceBackend.write(canonicalSidecarPath, {\n content: JSON.stringify(canonicalSidecar),\n });\n\n return result;\n }\n\n // ── AFSModule.delete ──────────────────────────────────────────────────────\n\n async delete(path: string, options?: AFSDeleteOptions): Promise<AFSDeleteResult> {\n // 1. Reconstruct full path and path safety check\n const fullPath = this.toFullPath(path);\n const normPath = assertPathSafe(fullPath);\n\n // 2. Find matching collection\n const match = matchCollections(this.opts.collections, normPath);\n if (!match) {\n // Non-matching path: pass-through to instanceBackend\n if (!this.opts.instanceBackend.delete) {\n return {};\n }\n return this.opts.instanceBackend.delete(fullPath, options);\n }\n\n const { collection } = match;\n\n // 3. Read canonical sidecar to get author_did for ownItem gate\n const canonicalSidecarPath = deriveSidecarPath(collection.canonical, fullPath);\n let provenanceAuthorDid: string | null = null;\n\n if (this.opts.instanceBackend.read) {\n try {\n const sidecarResult = await this.opts.instanceBackend.read(canonicalSidecarPath);\n const raw = sidecarResult.data?.content;\n if (typeof raw === \"string\") {\n const sidecar = JSON.parse(raw) as { author_did?: string | null };\n provenanceAuthorDid = sidecar.author_did ?? null;\n }\n } catch {\n // Sidecar missing — author unknown, ownItem gate cannot be satisfied\n }\n }\n\n // 4. Delete gate\n const callerDid = this.opts.callerDid;\n const anyItemRoleStr = collection.delete?.anyItem ?? \"admin\";\n\n const isOwnItem =\n callerDid != null && provenanceAuthorDid != null && callerDid === provenanceAuthorDid;\n const hasAnyItemRole = roleLevel(this.opts.callerRole) >= roleLevel(anyItemRoleStr);\n\n if (!isOwnItem && !hasAnyItemRole) {\n throw new AFSForbiddenError(\n fullPath,\n `insufficient permission to delete: role='${this.opts.callerRole ?? \"guest\"}'`,\n );\n }\n\n // 5. Delete canonical item (no cascade — author copy is untouched)\n if (!this.opts.instanceBackend.delete) {\n return {};\n }\n const result = await this.opts.instanceBackend.delete(fullPath, options);\n\n // Delete canonical-side sidecar (best-effort; failure is benign)\n try {\n await this.opts.instanceBackend.delete(canonicalSidecarPath);\n } catch {\n // Sidecar already gone or delete unsupported — benign\n }\n\n return result;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/session/role-level.ts
|
|
3
|
+
/**
|
|
4
|
+
* roleLevel — ordered numeric representation of the four ARC caller roles.
|
|
5
|
+
*
|
|
6
|
+
* Guest (0) is the fail-closed floor: undefined / unknown / empty → 0.
|
|
7
|
+
* This mirrors SettingsResolver's "unknown role → non-admin" fail-closed rule,
|
|
8
|
+
* generalised to a four-level ordinal. See design.md §5.5.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Map a caller role string to its numeric ordinal.
|
|
12
|
+
* Uses a switch rather than an object lookup to guard against prototype pollution.
|
|
13
|
+
*
|
|
14
|
+
* guest < member < admin < owner (0 < 1 < 2 < 3).
|
|
15
|
+
* Any unrecognised or absent value returns 0 (= guest / fail-closed).
|
|
16
|
+
*/
|
|
17
|
+
function roleLevel(r) {
|
|
18
|
+
switch (r) {
|
|
19
|
+
case "guest": return 0;
|
|
20
|
+
case "member": return 1;
|
|
21
|
+
case "admin": return 2;
|
|
22
|
+
case "owner": return 3;
|
|
23
|
+
default: return 0;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Compare two role strings by level.
|
|
28
|
+
* Returns negative if a < b, 0 if equal, positive if a > b.
|
|
29
|
+
*/
|
|
30
|
+
function compareRole(a, b) {
|
|
31
|
+
return roleLevel(a) - roleLevel(b);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
exports.compareRole = compareRole;
|
|
36
|
+
exports.roleLevel = roleLevel;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/session/role-level.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* roleLevel — ordered numeric representation of the four ARC caller roles.
|
|
4
|
+
*
|
|
5
|
+
* Guest (0) is the fail-closed floor: undefined / unknown / empty → 0.
|
|
6
|
+
* This mirrors SettingsResolver's "unknown role → non-admin" fail-closed rule,
|
|
7
|
+
* generalised to a four-level ordinal. See design.md §5.5.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Map a caller role string to its numeric ordinal.
|
|
11
|
+
* Uses a switch rather than an object lookup to guard against prototype pollution.
|
|
12
|
+
*
|
|
13
|
+
* guest < member < admin < owner (0 < 1 < 2 < 3).
|
|
14
|
+
* Any unrecognised or absent value returns 0 (= guest / fail-closed).
|
|
15
|
+
*/
|
|
16
|
+
declare function roleLevel(r?: string): number;
|
|
17
|
+
/**
|
|
18
|
+
* Compare two role strings by level.
|
|
19
|
+
* Returns negative if a < b, 0 if equal, positive if a > b.
|
|
20
|
+
*/
|
|
21
|
+
declare function compareRole(a?: string, b?: string): number;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { compareRole, roleLevel };
|
|
24
|
+
//# sourceMappingURL=role-level.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"role-level.d.cts","names":[],"sources":["../../src/session/role-level.ts"],"mappings":";;AAeA;;;;;AAmBA;;;;;;;;iBAnBgB,SAAA,CAAU,CAAA;;;;;iBAmBV,WAAA,CAAY,CAAA,WAAY,CAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/session/role-level.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* roleLevel — ordered numeric representation of the four ARC caller roles.
|
|
4
|
+
*
|
|
5
|
+
* Guest (0) is the fail-closed floor: undefined / unknown / empty → 0.
|
|
6
|
+
* This mirrors SettingsResolver's "unknown role → non-admin" fail-closed rule,
|
|
7
|
+
* generalised to a four-level ordinal. See design.md §5.5.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Map a caller role string to its numeric ordinal.
|
|
11
|
+
* Uses a switch rather than an object lookup to guard against prototype pollution.
|
|
12
|
+
*
|
|
13
|
+
* guest < member < admin < owner (0 < 1 < 2 < 3).
|
|
14
|
+
* Any unrecognised or absent value returns 0 (= guest / fail-closed).
|
|
15
|
+
*/
|
|
16
|
+
declare function roleLevel(r?: string): number;
|
|
17
|
+
/**
|
|
18
|
+
* Compare two role strings by level.
|
|
19
|
+
* Returns negative if a < b, 0 if equal, positive if a > b.
|
|
20
|
+
*/
|
|
21
|
+
declare function compareRole(a?: string, b?: string): number;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { compareRole, roleLevel };
|
|
24
|
+
//# sourceMappingURL=role-level.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"role-level.d.mts","names":[],"sources":["../../src/session/role-level.ts"],"mappings":";;AAeA;;;;;AAmBA;;;;;;;;iBAnBgB,SAAA,CAAU,CAAA;;;;;iBAmBV,WAAA,CAAY,CAAA,WAAY,CAAA"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//#region src/session/role-level.ts
|
|
2
|
+
/**
|
|
3
|
+
* roleLevel — ordered numeric representation of the four ARC caller roles.
|
|
4
|
+
*
|
|
5
|
+
* Guest (0) is the fail-closed floor: undefined / unknown / empty → 0.
|
|
6
|
+
* This mirrors SettingsResolver's "unknown role → non-admin" fail-closed rule,
|
|
7
|
+
* generalised to a four-level ordinal. See design.md §5.5.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Map a caller role string to its numeric ordinal.
|
|
11
|
+
* Uses a switch rather than an object lookup to guard against prototype pollution.
|
|
12
|
+
*
|
|
13
|
+
* guest < member < admin < owner (0 < 1 < 2 < 3).
|
|
14
|
+
* Any unrecognised or absent value returns 0 (= guest / fail-closed).
|
|
15
|
+
*/
|
|
16
|
+
function roleLevel(r) {
|
|
17
|
+
switch (r) {
|
|
18
|
+
case "guest": return 0;
|
|
19
|
+
case "member": return 1;
|
|
20
|
+
case "admin": return 2;
|
|
21
|
+
case "owner": return 3;
|
|
22
|
+
default: return 0;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Compare two role strings by level.
|
|
27
|
+
* Returns negative if a < b, 0 if equal, positive if a > b.
|
|
28
|
+
*/
|
|
29
|
+
function compareRole(a, b) {
|
|
30
|
+
return roleLevel(a) - roleLevel(b);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
export { compareRole, roleLevel };
|
|
35
|
+
//# sourceMappingURL=role-level.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"role-level.mjs","names":[],"sources":["../../src/session/role-level.ts"],"sourcesContent":["/**\n * roleLevel — ordered numeric representation of the four ARC caller roles.\n *\n * Guest (0) is the fail-closed floor: undefined / unknown / empty → 0.\n * This mirrors SettingsResolver's \"unknown role → non-admin\" fail-closed rule,\n * generalised to a four-level ordinal. See design.md §5.5.\n */\n\n/**\n * Map a caller role string to its numeric ordinal.\n * Uses a switch rather than an object lookup to guard against prototype pollution.\n *\n * guest < member < admin < owner (0 < 1 < 2 < 3).\n * Any unrecognised or absent value returns 0 (= guest / fail-closed).\n */\nexport function roleLevel(r?: string): number {\n switch (r) {\n case \"guest\":\n return 0;\n case \"member\":\n return 1;\n case \"admin\":\n return 2;\n case \"owner\":\n return 3;\n default:\n return 0;\n }\n}\n\n/**\n * Compare two role strings by level.\n * Returns negative if a < b, 0 if equal, positive if a > b.\n */\nexport function compareRole(a?: string, b?: string): number {\n return roleLevel(a) - roleLevel(b);\n}\n"],"mappings":";;;;;;;;;;;;;;;AAeA,SAAgB,UAAU,GAAoB;AAC5C,SAAQ,GAAR;EACE,KAAK,QACH,QAAO;EACT,KAAK,SACH,QAAO;EACT,KAAK,QACH,QAAO;EACT,KAAK,QACH,QAAO;EACT,QACE,QAAO;;;;;;;AAQb,SAAgB,YAAY,GAAY,GAAoB;AAC1D,QAAO,UAAU,EAAE,GAAG,UAAU,EAAE"}
|