@remnic/core 9.15.0 → 9.15.1
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/dist/access-admin-ops-surface.d.ts +4 -1
- package/dist/access-admin-ops-surface.js +18 -18
- package/dist/access-boundary.d.ts +4 -1
- package/dist/access-boundary.js +18 -19
- package/dist/access-cli.js +64 -66
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +2 -1
- package/dist/access-http.js +24 -25
- package/dist/access-identity-continuity-surface.js +18 -18
- package/dist/access-lcm-surface.d.ts +4 -1
- package/dist/access-lcm-surface.js +18 -18
- package/dist/access-mcp.d.ts +4 -1
- package/dist/access-mcp.js +23 -24
- package/dist/access-namespace-preflight-DgJtZCmU.d.ts +146 -0
- package/dist/access-namespace-preflight.d.ts +8 -0
- package/dist/access-namespace-preflight.js +158 -0
- package/dist/access-observe-write-surface.d.ts +4 -1
- package/dist/access-observe-write-surface.js +18 -18
- package/dist/access-operations-batch.js +20 -21
- package/dist/access-operations.d.ts +5 -2
- package/dist/access-operations.js +22 -23
- package/dist/access-recall-concurrency.d.ts +4 -1
- package/dist/access-recall-concurrency.js +18 -18
- package/dist/access-recall-response.d.ts +4 -1
- package/dist/access-recall-response.js +18 -18
- package/dist/access-recall-surface.d.ts +4 -1
- package/dist/access-recall-surface.js +18 -18
- package/dist/access-schema.js +3 -3
- package/dist/{access-service-DbZ1G3zV.d.ts → access-service-bw2p9ULX.d.ts} +6 -114
- package/dist/access-service.d.ts +4 -1
- package/dist/access-service.js +18 -18
- package/dist/access-surface-catalog.d.ts +4 -1
- package/dist/active-recall.js +1 -1
- package/dist/briefing.js +5 -5
- package/dist/{capsule-crypto-GWVG7LGC.js → capsule-crypto-7FJQINUR.js} +2 -2
- package/dist/capsule-crypto-7FJQINUR.js.map +1 -0
- package/dist/causal-consolidation.js +6 -6
- package/dist/{chunk-OWHERGF2.js → chunk-2NLLXCJG.js} +2 -2
- package/dist/{chunk-5RZHHANR.js → chunk-63P3PJED.js} +50 -50
- package/dist/{chunk-ALSGMEJR.js → chunk-7CPOZH6Y.js} +5 -5
- package/dist/{chunk-TY6NBWV4.js → chunk-7FNHLCQE.js} +2 -2
- package/dist/{chunk-MMGGVZ4Q.js → chunk-7VZWZVYG.js} +40 -18
- package/dist/{chunk-MMGGVZ4Q.js.map → chunk-7VZWZVYG.js.map} +1 -1
- package/dist/{chunk-HLG7VAZP.js → chunk-BOP256DB.js} +2 -2
- package/dist/{chunk-7G7DVA2C.js → chunk-BVC242OS.js} +2 -2
- package/dist/{chunk-CAZ2SHEP.js → chunk-CCNSCRJ2.js} +2 -2
- package/dist/{chunk-3332THSR.js → chunk-CVZYPFEH.js} +4 -4
- package/dist/{chunk-SK7AMF2J.js → chunk-DETNBTZS.js} +3 -3
- package/dist/{chunk-226RNKRU.js → chunk-FFF4XIGB.js} +32 -32
- package/dist/{chunk-AS6UOOXF.js → chunk-FMMZQDCT.js} +2 -2
- package/dist/{chunk-YQQYS2K4.js → chunk-H3MDWWGN.js} +2 -2
- package/dist/{chunk-TC5HBIIT.js → chunk-HBYQAOED.js} +2 -2
- package/dist/{chunk-3HYWNJDA.js → chunk-HWFK24UD.js} +420 -42
- package/dist/chunk-HWFK24UD.js.map +1 -0
- package/dist/{chunk-YO3ZB3PJ.js → chunk-I7DUEPE2.js} +30 -15
- package/dist/chunk-I7DUEPE2.js.map +1 -0
- package/dist/{chunk-YUQOAKPX.js → chunk-JRXN5LYL.js} +5 -5
- package/dist/{chunk-5W7CTZ2E.js → chunk-KQAFEZQX.js} +4 -4
- package/dist/{chunk-PYHOAXTI.js → chunk-LRGSLV7M.js} +4 -4
- package/dist/{chunk-A56B2AJR.js → chunk-MUQ3BD2C.js} +3 -3
- package/dist/{chunk-QJAQNXWI.js → chunk-ONHRRJZM.js} +6 -6
- package/dist/{chunk-DF552QUN.js → chunk-QCLQ4BGA.js} +5 -5
- package/dist/{chunk-IAHNKI2E.js → chunk-QDNE35FU.js} +2 -2
- package/dist/{chunk-W2OST2HH.js → chunk-QLOOCKKE.js} +2 -2
- package/dist/{chunk-I4NJ2CDY.js → chunk-RRHEV5HA.js} +4 -6
- package/dist/{chunk-I4NJ2CDY.js.map → chunk-RRHEV5HA.js.map} +1 -1
- package/dist/{chunk-7HZMTXXB.js → chunk-RWSLO24I.js} +10 -10
- package/dist/{chunk-UDIJMITR.js → chunk-SDZWOEZH.js} +22 -22
- package/dist/chunk-SDZWOEZH.js.map +1 -0
- package/dist/{chunk-QZGN4773.js → chunk-SW2KEZNX.js} +3 -3
- package/dist/{chunk-O4K3DWK2.js → chunk-SWVW42CG.js} +2 -2
- package/dist/{chunk-UVYI6VIX.js → chunk-X7Y7WX73.js} +1 -1
- package/dist/{chunk-CQ6WP3PA.js → chunk-XII2XSHL.js} +5 -5
- package/dist/{chunk-YSDVEPV2.js → chunk-XKFXY3ZA.js} +10 -10
- package/dist/{chunk-AVPXXV6J.js → chunk-XKGPIWGI.js} +4 -4
- package/dist/{chunk-IQFS4AU7.js → chunk-YEU4VEU4.js} +2 -2
- package/dist/{chunk-EZ25VE3G.js → chunk-YNDLCWXS.js} +4 -4
- package/dist/{chunk-Y6XODVJ4.js → chunk-YS7KIG3A.js} +8 -8
- package/dist/{chunk-XRNU7OEQ.js → chunk-ZNFVYUYF.js} +3 -3
- package/dist/{cli-5tNLVTvJ.d.ts → cli-DzPVFfGe.d.ts} +1 -1
- package/dist/cli.d.ts +5 -2
- package/dist/cli.js +45 -46
- package/dist/compounding/engine.js +5 -5
- package/dist/config.js +1 -1
- package/dist/connectors/codex-materialize-runner.js +5 -5
- package/dist/connectors/index.js +5 -5
- package/dist/contradiction/index.js +4 -4
- package/dist/dashboard-runtime.js +2 -2
- package/dist/entity-retrieval.js +5 -5
- package/dist/index.d.ts +6 -5
- package/dist/index.js +76 -78
- package/dist/lcm/index.js +3 -3
- package/dist/maintenance/memory-governance.js +5 -5
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +5 -5
- package/dist/maintenance/rebuild-memory-projection.js +6 -6
- package/dist/mcp-memory-inspector-app.d.ts +4 -1
- package/dist/namespaces/migrate.js +11 -11
- package/dist/namespaces/search.js +5 -5
- package/dist/namespaces/storage.js +5 -5
- package/dist/offline-sync-impression-drain.js +4 -4
- package/dist/operator-toolkit.js +16 -16
- package/dist/orchestration/maintenance.js +9 -9
- package/dist/orchestrator.js +65 -66
- package/dist/qmd.js +4 -4
- package/dist/resume-bundles.js +1 -1
- package/dist/schemas.d.ts +22 -22
- package/dist/search/factory.js +4 -4
- package/dist/search/index.js +6 -6
- package/dist/semantic-consolidation.js +6 -6
- package/dist/semantic-rule-promotion.js +5 -5
- package/dist/semantic-rule-verifier.js +5 -5
- package/dist/storage.js +4 -4
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/capsule-import.js +2 -2
- package/dist/transfer/import-sqlite.js +2 -2
- package/dist/transfer/types.d.ts +12 -12
- package/dist/verified-recall.js +5 -5
- package/package.json +2 -2
- package/src/access-http.test.ts +91 -0
- package/src/access-http.ts +13 -14
- package/src/access-namespace-preflight.test.ts +111 -0
- package/src/access-namespace-preflight.ts +111 -0
- package/src/access-operations-batch.ts +25 -2
- package/src/access-service-observe-scope.test.ts +81 -0
- package/src/access-service.ts +64 -32
- package/src/scopes/scope-plan.ts +66 -0
- package/dist/chunk-3HYWNJDA.js.map +0 -1
- package/dist/chunk-KF5EUVKQ.js +0 -289
- package/dist/chunk-KF5EUVKQ.js.map +0 -1
- package/dist/chunk-UDIJMITR.js.map +0 -1
- package/dist/chunk-YO3ZB3PJ.js.map +0 -1
- /package/dist/{capsule-crypto-GWVG7LGC.js.map → access-namespace-preflight.js.map} +0 -0
- /package/dist/{chunk-OWHERGF2.js.map → chunk-2NLLXCJG.js.map} +0 -0
- /package/dist/{chunk-5RZHHANR.js.map → chunk-63P3PJED.js.map} +0 -0
- /package/dist/{chunk-ALSGMEJR.js.map → chunk-7CPOZH6Y.js.map} +0 -0
- /package/dist/{chunk-TY6NBWV4.js.map → chunk-7FNHLCQE.js.map} +0 -0
- /package/dist/{chunk-HLG7VAZP.js.map → chunk-BOP256DB.js.map} +0 -0
- /package/dist/{chunk-7G7DVA2C.js.map → chunk-BVC242OS.js.map} +0 -0
- /package/dist/{chunk-CAZ2SHEP.js.map → chunk-CCNSCRJ2.js.map} +0 -0
- /package/dist/{chunk-3332THSR.js.map → chunk-CVZYPFEH.js.map} +0 -0
- /package/dist/{chunk-SK7AMF2J.js.map → chunk-DETNBTZS.js.map} +0 -0
- /package/dist/{chunk-226RNKRU.js.map → chunk-FFF4XIGB.js.map} +0 -0
- /package/dist/{chunk-AS6UOOXF.js.map → chunk-FMMZQDCT.js.map} +0 -0
- /package/dist/{chunk-YQQYS2K4.js.map → chunk-H3MDWWGN.js.map} +0 -0
- /package/dist/{chunk-TC5HBIIT.js.map → chunk-HBYQAOED.js.map} +0 -0
- /package/dist/{chunk-YUQOAKPX.js.map → chunk-JRXN5LYL.js.map} +0 -0
- /package/dist/{chunk-5W7CTZ2E.js.map → chunk-KQAFEZQX.js.map} +0 -0
- /package/dist/{chunk-PYHOAXTI.js.map → chunk-LRGSLV7M.js.map} +0 -0
- /package/dist/{chunk-A56B2AJR.js.map → chunk-MUQ3BD2C.js.map} +0 -0
- /package/dist/{chunk-QJAQNXWI.js.map → chunk-ONHRRJZM.js.map} +0 -0
- /package/dist/{chunk-DF552QUN.js.map → chunk-QCLQ4BGA.js.map} +0 -0
- /package/dist/{chunk-IAHNKI2E.js.map → chunk-QDNE35FU.js.map} +0 -0
- /package/dist/{chunk-W2OST2HH.js.map → chunk-QLOOCKKE.js.map} +0 -0
- /package/dist/{chunk-7HZMTXXB.js.map → chunk-RWSLO24I.js.map} +0 -0
- /package/dist/{chunk-QZGN4773.js.map → chunk-SW2KEZNX.js.map} +0 -0
- /package/dist/{chunk-O4K3DWK2.js.map → chunk-SWVW42CG.js.map} +0 -0
- /package/dist/{chunk-UVYI6VIX.js.map → chunk-X7Y7WX73.js.map} +0 -0
- /package/dist/{chunk-CQ6WP3PA.js.map → chunk-XII2XSHL.js.map} +0 -0
- /package/dist/{chunk-YSDVEPV2.js.map → chunk-XKFXY3ZA.js.map} +0 -0
- /package/dist/{chunk-AVPXXV6J.js.map → chunk-XKGPIWGI.js.map} +0 -0
- /package/dist/{chunk-IQFS4AU7.js.map → chunk-YEU4VEU4.js.map} +0 -0
- /package/dist/{chunk-EZ25VE3G.js.map → chunk-YNDLCWXS.js.map} +0 -0
- /package/dist/{chunk-Y6XODVJ4.js.map → chunk-YS7KIG3A.js.map} +0 -0
- /package/dist/{chunk-XRNU7OEQ.js.map → chunk-ZNFVYUYF.js.map} +0 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { getOperation } from "./access-boundary.js";
|
|
2
|
+
import { capabilityAllowsOp, isNamespaceAllowed, type TokenCapabilities } from "./access-token-capabilities.js";
|
|
3
|
+
import { EngramAccessForbiddenError, EngramAccessInputError, NamespaceNotWritableError } from "./access-errors.js";
|
|
4
|
+
import type { WritableNamespaceResult } from "./scopes/scope-plan.js";
|
|
5
|
+
|
|
6
|
+
export interface EngramAccessNamespaceWritableRequest {
|
|
7
|
+
namespace?: string;
|
|
8
|
+
sessionKey?: string;
|
|
9
|
+
authenticatedPrincipal?: string;
|
|
10
|
+
cwd?: string;
|
|
11
|
+
projectTag?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function namespaceWritableRequest(
|
|
15
|
+
params: URLSearchParams,
|
|
16
|
+
authenticatedPrincipal?: string,
|
|
17
|
+
): EngramAccessNamespaceWritableRequest {
|
|
18
|
+
const namespace = params.get("namespace") || undefined;
|
|
19
|
+
return {
|
|
20
|
+
namespace,
|
|
21
|
+
sessionKey: params.get("session") || undefined,
|
|
22
|
+
authenticatedPrincipal,
|
|
23
|
+
cwd: params.get("cwd") || undefined,
|
|
24
|
+
projectTag: params.get("projectTag") || undefined,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function parseNamespacePreflightWriteOp(
|
|
29
|
+
value: string | undefined,
|
|
30
|
+
): "observe" | "memory_store" {
|
|
31
|
+
if (value === undefined || value === "observe") return "observe";
|
|
32
|
+
if (value === "memory_store") return "memory_store";
|
|
33
|
+
throw new EngramAccessInputError(`unsupported namespace preflight operation: ${value}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function assertNamespacePreflightPermitted(
|
|
37
|
+
caps: TokenCapabilities | undefined | null,
|
|
38
|
+
): void {
|
|
39
|
+
const allowedOps = getOperation("namespace_writable")?.spec.allowedByOps ?? [];
|
|
40
|
+
if (!allowedOps.some((op) => capabilityAllowsOp(caps, op))) {
|
|
41
|
+
throw new EngramAccessForbiddenError("token is not permitted to run the namespace preflight");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type NamespaceWritableResolver = (
|
|
46
|
+
request: EngramAccessNamespaceWritableRequest,
|
|
47
|
+
) => Promise<string>;
|
|
48
|
+
export type NamespaceWritablePreflightResolver = (
|
|
49
|
+
request: EngramAccessNamespaceWritableRequest,
|
|
50
|
+
) => Promise<WritableNamespaceResult>;
|
|
51
|
+
|
|
52
|
+
export async function resolveNamespaceWritablePreflight(
|
|
53
|
+
request: EngramAccessNamespaceWritableRequest,
|
|
54
|
+
resolveWritable: NamespaceWritableResolver,
|
|
55
|
+
): Promise<WritableNamespaceResult> {
|
|
56
|
+
try {
|
|
57
|
+
return { ok: true, namespace: await resolveWritable(request) };
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (error instanceof NamespaceNotWritableError) {
|
|
60
|
+
return { ok: false, reason: "not_writable", namespace: error.attemptedNamespace };
|
|
61
|
+
}
|
|
62
|
+
if (error instanceof EngramAccessInputError) {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
reason: "unsupported",
|
|
66
|
+
namespace: request.namespace?.trim() ?? "",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function resolveAuthorizedNamespaceWritablePreflight(
|
|
74
|
+
caps: TokenCapabilities | undefined | null,
|
|
75
|
+
request: EngramAccessNamespaceWritableRequest,
|
|
76
|
+
defaultNamespace: string,
|
|
77
|
+
writeOp: "observe" | "memory_store",
|
|
78
|
+
resolvePreflight: NamespaceWritablePreflightResolver,
|
|
79
|
+
): Promise<WritableNamespaceResult> {
|
|
80
|
+
if (!capabilityAllowsOp(caps, writeOp)) {
|
|
81
|
+
return {
|
|
82
|
+
ok: false,
|
|
83
|
+
reason: "not_writable",
|
|
84
|
+
namespace: request.namespace?.trim() || defaultNamespace,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const result = await resolvePreflight(request);
|
|
89
|
+
if (!isNamespaceAllowed(caps, result.namespace, defaultNamespace)) {
|
|
90
|
+
|
|
91
|
+
return { ok: false, reason: "not_writable", namespace: result.namespace };
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function resolveQueryNamespaceWritablePreflight(
|
|
97
|
+
caps: TokenCapabilities | undefined | null,
|
|
98
|
+
params: URLSearchParams,
|
|
99
|
+
authenticatedPrincipal: string | undefined,
|
|
100
|
+
defaultNamespace: string,
|
|
101
|
+
resolvePreflight: NamespaceWritablePreflightResolver,
|
|
102
|
+
): Promise<WritableNamespaceResult> {
|
|
103
|
+
assertNamespacePreflightPermitted(caps);
|
|
104
|
+
return resolveAuthorizedNamespaceWritablePreflight(
|
|
105
|
+
caps,
|
|
106
|
+
namespaceWritableRequest(params, authenticatedPrincipal),
|
|
107
|
+
defaultNamespace,
|
|
108
|
+
parseNamespacePreflightWriteOp(params.get("op") ?? undefined),
|
|
109
|
+
resolvePreflight,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
@@ -18,7 +18,11 @@ import { EngramAccessForbiddenError } from "./access-errors.js";
|
|
|
18
18
|
import { EngramAccessInputError, type EngramAccessService } from "./access-service.js";
|
|
19
19
|
import { enforceNamespaceAllowList, tokenCapabilityStore } from "./access-token-capabilities.js";
|
|
20
20
|
import { projectTagProjectId } from "./coding/coding-namespace.js";
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
parseNamespacePreflightWriteOp,
|
|
23
|
+
resolveAuthorizedNamespaceWritablePreflight,
|
|
24
|
+
type EngramAccessNamespaceWritableRequest,
|
|
25
|
+
} from "./access-namespace-preflight.js";
|
|
22
26
|
import { expandTildePath } from "./utils/path.js";
|
|
23
27
|
import { resolvePrincipal } from "./namespaces/principal.js";
|
|
24
28
|
import { getRecallTimingStatus, isRecallTimingsOperator } from "./recall-timings.js";
|
|
@@ -114,7 +118,26 @@ defineOperation({ name: "recall_xray", description: "X-ray recall.", schema: str
|
|
|
114
118
|
return { result: await ctx.service.recallXray({ query: defStr(input.query, ""), sessionKey: optStr(input.sessionKey), namespace: optStr(input.namespace), budget, authenticatedPrincipal: ctx.authenticatedPrincipal, ...(dr && dr !== "" ? { disclosure: dr as RecallDisclosure } : {}), ...(ctx.abortSignal ? { abortSignal: ctx.abortSignal } : {}) }) };
|
|
115
119
|
},
|
|
116
120
|
});
|
|
117
|
-
defineOperation({ name: "namespace_writable", description: "Read-only preflight: is a namespace writable for the caller's principal?", allowedByOps: ["namespace_writable", "observe", "memory_store"], schema: strictSchema({ namespace: S.str, sessionKey: S.str, op: S.str
|
|
121
|
+
defineOperation({ name: "namespace_writable", description: "Read-only preflight: is a namespace writable for the caller's principal?", allowedByOps: ["namespace_writable", "observe", "memory_store"], schema: strictSchema({ namespace: S.str, sessionKey: S.str, op: S.str, cwd: S.str, projectTag: S.str }), handler: async (input, ctx) => {
|
|
122
|
+
const writeOp = parseNamespacePreflightWriteOp(optStr(input.op));
|
|
123
|
+
const namespace = optStr(input.namespace);
|
|
124
|
+
const request: EngramAccessNamespaceWritableRequest = {
|
|
125
|
+
sessionKey: optStr(input.sessionKey),
|
|
126
|
+
authenticatedPrincipal: ctx.authenticatedPrincipal,
|
|
127
|
+
cwd: optStr(input.cwd),
|
|
128
|
+
projectTag: optStr(input.projectTag),
|
|
129
|
+
...(namespace ? { namespace } : {}),
|
|
130
|
+
};
|
|
131
|
+
return {
|
|
132
|
+
result: await resolveAuthorizedNamespaceWritablePreflight(
|
|
133
|
+
tokenCapabilityStore.getStore(),
|
|
134
|
+
request,
|
|
135
|
+
ctx.service.configRef.defaultNamespace,
|
|
136
|
+
writeOp,
|
|
137
|
+
(preflightRequest) => ctx.service.namespaceWritablePreflight(preflightRequest),
|
|
138
|
+
),
|
|
139
|
+
};
|
|
140
|
+
} });
|
|
118
141
|
|
|
119
142
|
// === WEARABLES ===
|
|
120
143
|
defineOperation({ name: "wearables_status", description: "Wearables status.", schema: strictSchema({}), handler: async (_i, ctx) => ({ result: await ctx.service.wearablesStatus() }) });
|
|
@@ -33,6 +33,8 @@ import { join } from "node:path";
|
|
|
33
33
|
import test from "node:test";
|
|
34
34
|
|
|
35
35
|
import { EngramAccessService } from "./access-service.js";
|
|
36
|
+
import { tokenCapabilityStore } from "./access-token-capabilities.js";
|
|
37
|
+
import { resolveAuthorizedNamespaceWritablePreflight } from "./access-namespace-preflight.js";
|
|
36
38
|
import { Orchestrator } from "./orchestrator.js";
|
|
37
39
|
import type { StorageManager } from "./storage.js";
|
|
38
40
|
import type { EngramAccessObserveRequest } from "./access-service.js";
|
|
@@ -604,6 +606,85 @@ test("#1505 (cursor hAp) scopeDebug.baseNamespace reports the principal self bas
|
|
|
604
606
|
);
|
|
605
607
|
});
|
|
606
608
|
|
|
609
|
+
test("#2080 preflight resolves an implicit project scope through the observe write resolver", async () => {
|
|
610
|
+
const probe = makeObserveProbe({
|
|
611
|
+
...withSelfPolicyPrefix("pi-geek"),
|
|
612
|
+
namespacePolicies: [
|
|
613
|
+
{ name: "default", readPrincipals: ["admin"], writePrincipals: ["admin"] },
|
|
614
|
+
{ name: "pi-geek", readPrincipals: ["pi-geek"], writePrincipals: ["pi-geek"] },
|
|
615
|
+
],
|
|
616
|
+
});
|
|
617
|
+
const service = new EngramAccessService(probe.orch);
|
|
618
|
+
const expected = combineNamespaces(
|
|
619
|
+
"pi-geek",
|
|
620
|
+
projectNamespaceName(projectTagProjectId("Acme/Webshop")),
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
const result = await (
|
|
624
|
+
service as unknown as {
|
|
625
|
+
namespaceWritablePreflight: (request: {
|
|
626
|
+
sessionKey: string;
|
|
627
|
+
authenticatedPrincipal: string;
|
|
628
|
+
projectTag: string;
|
|
629
|
+
}) => Promise<{ ok: boolean; namespace: string }>;
|
|
630
|
+
}
|
|
631
|
+
).namespaceWritablePreflight({
|
|
632
|
+
sessionKey: "pi-geek:abc123",
|
|
633
|
+
authenticatedPrincipal: "pi-geek",
|
|
634
|
+
projectTag: "Acme/Webshop",
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
assert.deepEqual(result, { ok: true, namespace: expected });
|
|
638
|
+
await assert.rejects(
|
|
639
|
+
() =>
|
|
640
|
+
tokenCapabilityStore.run({ version: 1, namespaces: ["default"] }, () =>
|
|
641
|
+
(
|
|
642
|
+
service as unknown as {
|
|
643
|
+
resolveCodingScopedWriteNamespace: (request: {
|
|
644
|
+
sessionKey: string;
|
|
645
|
+
authenticatedPrincipal: string;
|
|
646
|
+
projectTag: string;
|
|
647
|
+
}) => Promise<string>;
|
|
648
|
+
}
|
|
649
|
+
).resolveCodingScopedWriteNamespace({
|
|
650
|
+
sessionKey: "pi-geek:abc123",
|
|
651
|
+
authenticatedPrincipal: "pi-geek",
|
|
652
|
+
projectTag: "Acme/Webshop",
|
|
653
|
+
}),
|
|
654
|
+
),
|
|
655
|
+
/not permitted/,
|
|
656
|
+
);
|
|
657
|
+
const deniedPreflight = await tokenCapabilityStore.run(
|
|
658
|
+
{ version: 1, ops: ["observe"], namespaces: ["default"] },
|
|
659
|
+
() =>
|
|
660
|
+
resolveAuthorizedNamespaceWritablePreflight(
|
|
661
|
+
tokenCapabilityStore.getStore(),
|
|
662
|
+
{
|
|
663
|
+
sessionKey: "pi-geek:abc123",
|
|
664
|
+
authenticatedPrincipal: "pi-geek",
|
|
665
|
+
projectTag: "Acme/Webshop",
|
|
666
|
+
},
|
|
667
|
+
"default",
|
|
668
|
+
"observe",
|
|
669
|
+
(request) => service.namespaceWritablePreflight(request),
|
|
670
|
+
),
|
|
671
|
+
);
|
|
672
|
+
assert.deepEqual(deniedPreflight, { ok: false, reason: "not_writable", namespace: expected });
|
|
673
|
+
await assert.rejects(
|
|
674
|
+
() =>
|
|
675
|
+
tokenCapabilityStore.run({ version: 1, namespaces: ["default"] }, () =>
|
|
676
|
+
service.observe(
|
|
677
|
+
observeRequest({
|
|
678
|
+
sessionKey: "pi-geek:abc123",
|
|
679
|
+
authenticatedPrincipal: "pi-geek",
|
|
680
|
+
projectTag: "Acme/Webshop",
|
|
681
|
+
}),
|
|
682
|
+
),
|
|
683
|
+
),
|
|
684
|
+
/not permitted/,
|
|
685
|
+
);
|
|
686
|
+
});
|
|
687
|
+
|
|
607
688
|
test("#1495 the scope plan's writeNamespace matches resolveCodingScopedWriteNamespace (memory_store / suggestion_submit parity, rule 39)", async () => {
|
|
608
689
|
// Regression guard: observe's effective scope MUST be identical to what the
|
|
609
690
|
// explicit-write tools (memory_store / suggestion_submit) resolve via
|
package/src/access-service.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { readdirSync, unlinkSync } from "node:fs";
|
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
7
|
import { ZodError } from "zod";
|
|
8
8
|
import { AccessIdempotencyStore, hashAccessIdempotencyPayload } from "./access-idempotency.js";
|
|
9
|
+
import { enforceNamespaceAllowList, tokenCapabilityStore } from "./access-token-capabilities.js";
|
|
9
10
|
import {
|
|
10
11
|
recordCitationUsage as recordCitationUsageForAccess,
|
|
11
12
|
type CitationUsageRequest,
|
|
@@ -130,8 +131,11 @@ import {
|
|
|
130
131
|
import {
|
|
131
132
|
type ScopePlan,
|
|
132
133
|
resolveScopePlan,
|
|
134
|
+
resolveScopedWritableNamespaceValue,
|
|
133
135
|
resolveWritableNamespaceValue,
|
|
136
|
+
type WritableNamespaceResult,
|
|
134
137
|
} from "./scopes/scope-plan.js";
|
|
138
|
+
import { resolveNamespaceWritablePreflight, type EngramAccessNamespaceWritableRequest } from "./access-namespace-preflight.js";
|
|
135
139
|
import { namespaceIdentityFromToken } from "./namespaces/identity.js";
|
|
136
140
|
import { namespaceCollectionName } from "./namespaces/search.js";
|
|
137
141
|
import { SecureStoreLockedError } from "./secure-store/index.js";
|
|
@@ -958,7 +962,7 @@ export interface CodingScopedWriteInput {
|
|
|
958
962
|
cwd?: string;
|
|
959
963
|
projectTag?: string;
|
|
960
964
|
}
|
|
961
|
-
|
|
965
|
+
export type { EngramAccessNamespaceWritableRequest } from "./access-namespace-preflight.js";
|
|
962
966
|
/**
|
|
963
967
|
* Internal, single-resolution plan describing the effective memory scope for a
|
|
964
968
|
* write-producing access request (#1495, seed for epic #1494). One plan is
|
|
@@ -1588,6 +1592,14 @@ export class EngramAccessService {
|
|
|
1588
1592
|
return { principal, codingContext, overlay, profilePlan };
|
|
1589
1593
|
}
|
|
1590
1594
|
|
|
1595
|
+
private assertTokenCanWriteNamespace(namespace: string): void {
|
|
1596
|
+
enforceNamespaceAllowList(
|
|
1597
|
+
tokenCapabilityStore.getStore(),
|
|
1598
|
+
namespace,
|
|
1599
|
+
this.orchestrator.config.defaultNamespace,
|
|
1600
|
+
);
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1591
1603
|
/**
|
|
1592
1604
|
* Resolve the write namespace for explicit-write tools (memory_store /
|
|
1593
1605
|
* suggestion_submit), project-scoping the write the same way recall does so a
|
|
@@ -1603,49 +1615,56 @@ export class EngramAccessService {
|
|
|
1603
1615
|
sessionKey?: string;
|
|
1604
1616
|
authenticatedPrincipal?: string;
|
|
1605
1617
|
},
|
|
1618
|
+
enforceToken: boolean = true,
|
|
1606
1619
|
): Promise<string> {
|
|
1607
|
-
const
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
request.namespace,
|
|
1620
|
+
const requested = request.namespace?.trim();
|
|
1621
|
+
if (requested) {
|
|
1622
|
+
const namespace = this.writableNamespaceFor(
|
|
1623
|
+
requested,
|
|
1612
1624
|
request.sessionKey,
|
|
1613
1625
|
request.authenticatedPrincipal,
|
|
1614
1626
|
);
|
|
1627
|
+
if (enforceToken) this.assertTokenCanWriteNamespace(namespace);
|
|
1628
|
+
return namespace;
|
|
1615
1629
|
}
|
|
1630
|
+
|
|
1616
1631
|
const { principal, overlay, profilePlan } = await this.resolveCodingScopeInputs(request);
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1632
|
+
const result = resolveScopedWritableNamespaceValue({
|
|
1633
|
+
sessionKey: request.sessionKey,
|
|
1634
|
+
authenticatedPrincipal: request.authenticatedPrincipal,
|
|
1635
|
+
principal,
|
|
1636
|
+
codingOverlay: overlay,
|
|
1637
|
+
scopeProfile: profilePlan,
|
|
1638
|
+
config: this.orchestrator.config,
|
|
1639
|
+
});
|
|
1640
|
+
if (result.ok) {
|
|
1641
|
+
if (enforceToken) this.assertTokenCanWriteNamespace(result.namespace);
|
|
1642
|
+
return result.namespace;
|
|
1627
1643
|
}
|
|
1628
|
-
if (
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
request.sessionKey,
|
|
1634
|
-
request.authenticatedPrincipal,
|
|
1644
|
+
if (profilePlan && result.namespace === profilePlan.writeNamespace) {
|
|
1645
|
+
throw new NamespaceNotWritableError(
|
|
1646
|
+
result.namespace,
|
|
1647
|
+
principal,
|
|
1648
|
+
`scope profile ${profilePlan.profileId} has no writable layer for principal ${principal ?? "anonymous"}`,
|
|
1635
1649
|
);
|
|
1636
1650
|
}
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
// no separate write policy.
|
|
1641
|
-
const base = defaultNamespaceForPrincipal(principal, this.orchestrator.config);
|
|
1642
|
-
if (!canWriteNamespace(principal, base, this.orchestrator.config)) {
|
|
1643
|
-
throw new NamespaceNotWritableError(base, principal);
|
|
1651
|
+
|
|
1652
|
+
if (result.reason === "unsupported") {
|
|
1653
|
+
throw new EngramAccessInputError(`unsupported namespace: ${result.namespace}`);
|
|
1644
1654
|
}
|
|
1645
|
-
|
|
1655
|
+
throw new NamespaceNotWritableError(result.namespace, principal);
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
async namespaceWritablePreflight(
|
|
1659
|
+
request: EngramAccessNamespaceWritableRequest,
|
|
1660
|
+
): Promise<WritableNamespaceResult> {
|
|
1661
|
+
return resolveNamespaceWritablePreflight(
|
|
1662
|
+
request,
|
|
1663
|
+
(preflightRequest) => this.resolveCodingScopedWriteNamespace(preflightRequest, false),
|
|
1664
|
+
);
|
|
1646
1665
|
}
|
|
1647
1666
|
|
|
1648
|
-
/**
|
|
1667
|
+
/**
|
|
1649
1668
|
* `list`/`get` use this so a record written by a project-scoped session is
|
|
1650
1669
|
* listable/fetchable by the SAME session without manually supplying the
|
|
1651
1670
|
* overlaid namespace (review P2). Derivation is IDENTICAL to the write path
|
|
@@ -1748,6 +1767,7 @@ export class EngramAccessService {
|
|
|
1748
1767
|
request.sessionKey,
|
|
1749
1768
|
request.authenticatedPrincipal,
|
|
1750
1769
|
);
|
|
1770
|
+
this.assertTokenCanWriteNamespace(writeNamespace);
|
|
1751
1771
|
return {
|
|
1752
1772
|
principal,
|
|
1753
1773
|
explicitNamespace: request.namespace!.trim(),
|
|
@@ -1825,6 +1845,15 @@ export class EngramAccessService {
|
|
|
1825
1845
|
this.orchestrator.setCodingContextForSession(request.sessionKey!, null);
|
|
1826
1846
|
}
|
|
1827
1847
|
};
|
|
1848
|
+
const assertWriteNamespaceAllowed = (namespace: string): void => {
|
|
1849
|
+
try {
|
|
1850
|
+
this.assertTokenCanWriteNamespace(namespace);
|
|
1851
|
+
} catch (error) {
|
|
1852
|
+
clearSeededContext();
|
|
1853
|
+
throw error;
|
|
1854
|
+
}
|
|
1855
|
+
};
|
|
1856
|
+
|
|
1828
1857
|
|
|
1829
1858
|
const overlaidBase = this.orchestrator.applyCodingNamespaceOverlay(
|
|
1830
1859
|
request.sessionKey,
|
|
@@ -1876,6 +1905,7 @@ export class EngramAccessService {
|
|
|
1876
1905
|
readNamespaces.includes(layer.namespace),
|
|
1877
1906
|
),
|
|
1878
1907
|
);
|
|
1908
|
+
assertWriteNamespaceAllowed(profilePlan.writeNamespace);
|
|
1879
1909
|
return {
|
|
1880
1910
|
principal,
|
|
1881
1911
|
baseNamespace: profilePlan.baseNamespace,
|
|
@@ -1928,6 +1958,7 @@ export class EngramAccessService {
|
|
|
1928
1958
|
clearSeededContext();
|
|
1929
1959
|
throw new NamespaceNotWritableError(baseNamespace, principal);
|
|
1930
1960
|
}
|
|
1961
|
+
assertWriteNamespaceAllowed(writeNamespace);
|
|
1931
1962
|
return {
|
|
1932
1963
|
principal,
|
|
1933
1964
|
// scopeDebug.baseNamespace must report the principal SELF base
|
|
@@ -1965,6 +1996,7 @@ export class EngramAccessService {
|
|
|
1965
1996
|
const ns = combineNamespaces(baseNamespace, fallback);
|
|
1966
1997
|
if (!readNamespaces.includes(ns)) readNamespaces.push(ns);
|
|
1967
1998
|
}
|
|
1999
|
+
assertWriteNamespaceAllowed(writeNamespace);
|
|
1968
2000
|
return {
|
|
1969
2001
|
principal,
|
|
1970
2002
|
baseNamespace,
|
package/src/scopes/scope-plan.ts
CHANGED
|
@@ -489,6 +489,72 @@ export function resolveWritableNamespaceValue(
|
|
|
489
489
|
return { ok: true, namespace: resolved };
|
|
490
490
|
}
|
|
491
491
|
|
|
492
|
+
/**
|
|
493
|
+
* Inputs already derived by the access-service's read-only coding-context
|
|
494
|
+
* resolver. Keeping context discovery outside this pure function lets writes
|
|
495
|
+
* and preflight checks apply identical namespace and ACL rules.
|
|
496
|
+
*/
|
|
497
|
+
export interface ScopedWritableNamespaceInput {
|
|
498
|
+
readonly namespace?: string;
|
|
499
|
+
readonly sessionKey?: string;
|
|
500
|
+
readonly authenticatedPrincipal?: string;
|
|
501
|
+
readonly principal: string | undefined;
|
|
502
|
+
readonly codingOverlay: CodingNamespaceOverlay | null;
|
|
503
|
+
readonly scopeProfile: ResolvedScopeProfilePlan | null;
|
|
504
|
+
readonly config: PluginConfig;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Resolve the namespace an explicit write or implicit coding-scoped write
|
|
509
|
+
* would use. The caller supplies the same derived coding inputs for the real
|
|
510
|
+
* write and the read-only preflight.
|
|
511
|
+
*/
|
|
512
|
+
export function resolveScopedWritableNamespaceValue(
|
|
513
|
+
input: ScopedWritableNamespaceInput,
|
|
514
|
+
): WritableNamespaceResult {
|
|
515
|
+
const requested = input.namespace?.trim();
|
|
516
|
+
if (requested) {
|
|
517
|
+
return resolveWritableNamespaceValue(
|
|
518
|
+
requested,
|
|
519
|
+
input.sessionKey,
|
|
520
|
+
input.authenticatedPrincipal,
|
|
521
|
+
input.config,
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
if (input.scopeProfile) {
|
|
525
|
+
const selectedLayer = input.scopeProfile.layers.find(
|
|
526
|
+
(layer) => layer.id === input.scopeProfile?.writeLayer,
|
|
527
|
+
);
|
|
528
|
+
const writeNamespaceReadable =
|
|
529
|
+
input.scopeProfile.writeNamespace.length > 0 &&
|
|
530
|
+
input.scopeProfile.readNamespaces.includes(input.scopeProfile.writeNamespace);
|
|
531
|
+
if (!selectedLayer?.writable || !writeNamespaceReadable) {
|
|
532
|
+
return {
|
|
533
|
+
ok: false,
|
|
534
|
+
reason: "not_writable",
|
|
535
|
+
namespace: input.scopeProfile.writeNamespace,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
return { ok: true, namespace: input.scopeProfile.writeNamespace };
|
|
539
|
+
}
|
|
540
|
+
if (!input.codingOverlay) {
|
|
541
|
+
return resolveWritableNamespaceValue(
|
|
542
|
+
undefined,
|
|
543
|
+
input.sessionKey,
|
|
544
|
+
input.authenticatedPrincipal,
|
|
545
|
+
input.config,
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
const baseNamespace = defaultNamespaceForPrincipal(input.principal, input.config);
|
|
549
|
+
if (!canWriteNamespace(input.principal, baseNamespace, input.config)) {
|
|
550
|
+
return { ok: false, reason: "not_writable", namespace: baseNamespace };
|
|
551
|
+
}
|
|
552
|
+
return {
|
|
553
|
+
ok: true,
|
|
554
|
+
namespace: combineNamespaces(baseNamespace, input.codingOverlay.namespace),
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
|
|
492
558
|
/**
|
|
493
559
|
* Namespace preflight (issue #1888 part 3): would a write to `namespace` by a
|
|
494
560
|
* caller carrying `caps` succeed? Combines the token's write-op scope, its
|