@substrat-run/contract-tests 0.116.0 → 0.117.0
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/capability-expiry-suite.d.ts +8 -0
- package/dist/capability-expiry-suite.d.ts.map +1 -0
- package/dist/capability-expiry-suite.js +131 -0
- package/dist/capability-expiry-suite.js.map +1 -0
- package/dist/capability-suite.d.ts +3 -0
- package/dist/capability-suite.d.ts.map +1 -0
- package/dist/capability-suite.js +498 -0
- package/dist/capability-suite.js.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/modules.d.ts +126 -0
- package/dist/modules.d.ts.map +1 -1
- package/dist/modules.js +273 -1
- package/dist/modules.js.map +1 -1
- package/dist/permission-suite.d.ts.map +1 -1
- package/dist/permission-suite.js +150 -2
- package/dist/permission-suite.js.map +1 -1
- package/dist/schedule-suite.d.ts +14 -0
- package/dist/schedule-suite.d.ts.map +1 -1
- package/dist/schedule-suite.js +22 -1
- package/dist/schedule-suite.js.map +1 -1
- package/dist/scope-host-suite.d.ts.map +1 -1
- package/dist/scope-host-suite.js +945 -12
- package/dist/scope-host-suite.js.map +1 -1
- package/dist/system-switch-suite.d.ts +16 -0
- package/dist/system-switch-suite.d.ts.map +1 -0
- package/dist/system-switch-suite.js +215 -0
- package/dist/system-switch-suite.js.map +1 -0
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -2,6 +2,8 @@ export { scopeHostContractSuite } from './scope-host-suite.js';
|
|
|
2
2
|
export { permissionContractSuite } from './permission-suite.js';
|
|
3
3
|
export { atomicContractSuite } from './atomic-suite.js';
|
|
4
4
|
export { impersonationContractSuite } from './impersonation-suite.js';
|
|
5
|
+
export { capabilityContractSuite } from './capability-suite.js';
|
|
6
|
+
export { capabilityExpiryContractSuite } from './capability-expiry-suite.js';
|
|
5
7
|
export { searchContractSuite } from './search-suite.js';
|
|
6
8
|
export { entityVersionContractSuite } from './entity-version-suite.js';
|
|
7
9
|
export { timelineContractSuite } from './timeline-suite.js';
|
|
@@ -10,6 +12,7 @@ export { idempotencyContractSuite } from './idempotency-suite.js';
|
|
|
10
12
|
export { listContractSuite } from './list-suite.js';
|
|
11
13
|
export { scheduleContractSuite } from './schedule-suite.js';
|
|
12
14
|
export { jobRunContractSuite } from './job-run-suite.js';
|
|
15
|
+
export { systemSwitchContractSuite } from './system-switch-suite.js';
|
|
13
16
|
export { inputParseContractSuite } from './input-parse-suite.js';
|
|
14
17
|
export { spineGuardContractSuite } from './spine-guard-suite.js';
|
|
15
18
|
export { grantExpiryContractSuite } from './grant-expiry-suite.js';
|
|
@@ -17,6 +20,6 @@ export { facetRecencyContractSuite } from './facet-recency-suite.js';
|
|
|
17
20
|
export { entityCheckConformanceSuite, planEntityCheckCoverage } from './entity-check-suite.js';
|
|
18
21
|
export { nodeOnlySuite, declaredNodeOnlySuite } from './node-only-suite.js';
|
|
19
22
|
export { declareEntityChecks, declareNodeOnly, assertNodeOnly } from './conformance.js';
|
|
20
|
-
export { atomicMod, billedMod, brokenMod, connectorMod, contractTestBareOps, contractTestInitialModules, contractTestModules, liveMod, liveModManifest, permMod, scheduleMod, jobsMod, testMod, freshnessMod, searchMod, searchModManifest, listMod, listModManifest, parseMod, parseModManifest, } from './modules.js';
|
|
23
|
+
export { atomicMod, billedMod, brokenMod, connectorMod, contractTestBareOps, contractTestInitialModules, contractTestModules, liveMod, liveModManifest, permMod, capMod, capModManifest, scheduleMod, jobsMod, testMod, freshnessMod, searchMod, searchModManifest, listMod, listModManifest, parseMod, parseModManifest, } from './modules.js';
|
|
21
24
|
export { connectorCalls, connectorTestFetch, resetConnectorCalls, } from './connector-fixture.js';
|
|
22
25
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,yBAAyB,EAA4B,MAAM,0BAA0B,CAAC;AAE/F,OAAO,EAAE,2BAA2B,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAE/F,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE5E,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAOxF,OAAO,EACL,SAAS,EACT,SAAS,EACT,SAAS,EACT,YAAY,EACZ,mBAAmB,EACnB,0BAA0B,EAC1B,mBAAmB,EACnB,OAAO,EACP,eAAe,EACf,OAAO,EACP,WAAW,EACX,OAAO,EACP,OAAO,EACP,YAAY,EACZ,SAAS,EACT,iBAAiB,EACjB,OAAO,EACP,eAAe,EACf,QAAQ,EACR,gBAAgB,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,mBAAmB,GAEpB,MAAM,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,6BAA6B,EAAE,MAAM,8BAA8B,CAAC;AAE7E,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,yBAAyB,EAA4B,MAAM,0BAA0B,CAAC;AAE/F,OAAO,EAAE,2BAA2B,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAE/F,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE5E,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAOxF,OAAO,EACL,SAAS,EACT,SAAS,EACT,SAAS,EACT,YAAY,EACZ,mBAAmB,EACnB,0BAA0B,EAC1B,mBAAmB,EACnB,OAAO,EACP,eAAe,EACf,OAAO,EACP,MAAM,EACN,cAAc,EACd,WAAW,EACX,OAAO,EACP,OAAO,EACP,YAAY,EACZ,SAAS,EACT,iBAAiB,EACjB,OAAO,EACP,eAAe,EACf,QAAQ,EACR,gBAAgB,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,mBAAmB,GAEpB,MAAM,wBAAwB,CAAC"}
|
package/dist/modules.d.ts
CHANGED
|
@@ -2466,6 +2466,132 @@ export declare const idempotencyModManifest: {
|
|
|
2466
2466
|
} | undefined;
|
|
2467
2467
|
};
|
|
2468
2468
|
export declare const idempotencyMod: ModuleRegistration;
|
|
2469
|
+
/**
|
|
2470
|
+
* The fixture for the capability suite: a folder tree (`doc` → `folder`, `folder` →
|
|
2471
|
+
* `folder`), three keys, and operations shaped the way a document vertical's would be —
|
|
2472
|
+
* a read and a write that check ONE entity, so what a capability can reach is decided by
|
|
2473
|
+
* the checker's walk and nothing else.
|
|
2474
|
+
*
|
|
2475
|
+
* The minting operations are deliberately UNGUARDED, like `perm/share`: the guardrails
|
|
2476
|
+
* under test live inside `ctx.capabilities`, and an operation-level check in front of
|
|
2477
|
+
* them would only prove the check.
|
|
2478
|
+
*/
|
|
2479
|
+
export declare const capModManifest: {
|
|
2480
|
+
id: string & z.$brand<"ModuleId">;
|
|
2481
|
+
version: string;
|
|
2482
|
+
kernelContract: string;
|
|
2483
|
+
permissions: {
|
|
2484
|
+
key: string & z.$brand<"PermissionKey">;
|
|
2485
|
+
description: string;
|
|
2486
|
+
}[];
|
|
2487
|
+
events: {
|
|
2488
|
+
emits: {
|
|
2489
|
+
type: string;
|
|
2490
|
+
schemaVersion: number;
|
|
2491
|
+
}[];
|
|
2492
|
+
consumes: {
|
|
2493
|
+
type: string;
|
|
2494
|
+
schemaVersion: number;
|
|
2495
|
+
}[];
|
|
2496
|
+
};
|
|
2497
|
+
migrations: {
|
|
2498
|
+
journalDir: string;
|
|
2499
|
+
compatibleFrom: string;
|
|
2500
|
+
};
|
|
2501
|
+
attachmentTargets: {
|
|
2502
|
+
entityType: string;
|
|
2503
|
+
readPermission: string & z.$brand<"PermissionKey">;
|
|
2504
|
+
writePermission?: (string & z.$brand<"PermissionKey">) | undefined;
|
|
2505
|
+
}[];
|
|
2506
|
+
liveTargets?: {
|
|
2507
|
+
entityType: string;
|
|
2508
|
+
readPermission: string & z.$brand<"PermissionKey">;
|
|
2509
|
+
}[] | undefined;
|
|
2510
|
+
entityRelations?: {
|
|
2511
|
+
entityType: string;
|
|
2512
|
+
parentType: string;
|
|
2513
|
+
}[] | undefined;
|
|
2514
|
+
guards?: {
|
|
2515
|
+
before: string;
|
|
2516
|
+
predicate: string;
|
|
2517
|
+
config: Record<string, unknown>;
|
|
2518
|
+
}[] | undefined;
|
|
2519
|
+
schedules?: {
|
|
2520
|
+
operation: string;
|
|
2521
|
+
cadence: {
|
|
2522
|
+
everyMinutes: number;
|
|
2523
|
+
};
|
|
2524
|
+
input?: Record<string, unknown> | undefined;
|
|
2525
|
+
permissions: (string & z.$brand<"PermissionKey">)[];
|
|
2526
|
+
}[] | undefined;
|
|
2527
|
+
freshness?: {
|
|
2528
|
+
eventType: string;
|
|
2529
|
+
within: {
|
|
2530
|
+
hours: number;
|
|
2531
|
+
};
|
|
2532
|
+
}[] | undefined;
|
|
2533
|
+
withdraws?: string[] | undefined;
|
|
2534
|
+
entitlementKey: string;
|
|
2535
|
+
envSpec?: {
|
|
2536
|
+
key: string;
|
|
2537
|
+
label?: string | undefined;
|
|
2538
|
+
description: string;
|
|
2539
|
+
placeholder?: string | undefined;
|
|
2540
|
+
required: boolean;
|
|
2541
|
+
secret: boolean;
|
|
2542
|
+
default?: string | undefined;
|
|
2543
|
+
group?: string | undefined;
|
|
2544
|
+
}[] | undefined;
|
|
2545
|
+
ownerGrants?: (string & z.$brand<"PermissionKey">)[] | undefined;
|
|
2546
|
+
entitlements?: string[] | undefined;
|
|
2547
|
+
provides?: string[] | undefined;
|
|
2548
|
+
requires?: string[] | undefined;
|
|
2549
|
+
api?: string | undefined;
|
|
2550
|
+
searchables?: {
|
|
2551
|
+
entityType: string;
|
|
2552
|
+
fields: string[];
|
|
2553
|
+
table?: string | undefined;
|
|
2554
|
+
idColumn?: string | undefined;
|
|
2555
|
+
tokenizer?: "prefix" | "substring" | undefined;
|
|
2556
|
+
}[] | undefined;
|
|
2557
|
+
lists?: {
|
|
2558
|
+
entityType: string;
|
|
2559
|
+
sortable: string[];
|
|
2560
|
+
filterable?: string[] | undefined;
|
|
2561
|
+
table?: string | undefined;
|
|
2562
|
+
idColumn?: string | undefined;
|
|
2563
|
+
}[] | undefined;
|
|
2564
|
+
ui?: {
|
|
2565
|
+
routes?: {
|
|
2566
|
+
path: string;
|
|
2567
|
+
screen: string;
|
|
2568
|
+
permission: string & z.$brand<"PermissionKey">;
|
|
2569
|
+
}[] | undefined;
|
|
2570
|
+
nav?: {
|
|
2571
|
+
label: string;
|
|
2572
|
+
icon?: string | undefined;
|
|
2573
|
+
to: string;
|
|
2574
|
+
permission: string & z.$brand<"PermissionKey">;
|
|
2575
|
+
}[] | undefined;
|
|
2576
|
+
entityViews?: {
|
|
2577
|
+
entityType: string;
|
|
2578
|
+
view: string;
|
|
2579
|
+
}[] | undefined;
|
|
2580
|
+
widgets?: {
|
|
2581
|
+
slot: string;
|
|
2582
|
+
component: string;
|
|
2583
|
+
permission: string & z.$brand<"PermissionKey">;
|
|
2584
|
+
}[] | undefined;
|
|
2585
|
+
settingsPanels?: {
|
|
2586
|
+
label: string;
|
|
2587
|
+
component: string;
|
|
2588
|
+
permission: string & z.$brand<"PermissionKey">;
|
|
2589
|
+
}[] | undefined;
|
|
2590
|
+
} | undefined;
|
|
2591
|
+
};
|
|
2592
|
+
/** Every channel through which a module could persist its own minted secret by accident. */
|
|
2593
|
+
export declare const CAP_LEAK_CHANNELS: readonly ['emit', 'key', 'entity', 'kind', 'intent', 'sql', 'bytes'];
|
|
2594
|
+
export declare const capMod: ModuleRegistration;
|
|
2469
2595
|
export declare const contractTestModules: ModuleRegistration[];
|
|
2470
2596
|
/**
|
|
2471
2597
|
* A module for the live-read fan-out, with two entity types that differ in exactly
|
package/dist/modules.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"modules.d.ts","sourceRoot":"","sources":["../src/modules.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"modules.d.ts","sourceRoot":"","sources":["../src/modules.ts"],"names":[],"mappings":"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"}
|
package/dist/modules.js
CHANGED
|
@@ -286,6 +286,10 @@ export const permModManifest = moduleManifest.parse({
|
|
|
286
286
|
permissions: [
|
|
287
287
|
{ key: 'perm:use', description: 'use the thing' },
|
|
288
288
|
{ key: 'perm:read', description: 'read the thing' },
|
|
289
|
+
// #1655: exactly PERMISSION_KEY_MAX_LENGTH (41) bytes, the longest key a manifest
|
|
290
|
+
// accepts — so both adapters run a real check of it, and the Durable-Object one
|
|
291
|
+
// builds `granted:<key>%` at the 50-byte LIKE limit it enforces and node does not.
|
|
292
|
+
{ key: 'perm:long-key-at-the-like-pattern-limit01', description: 'the longest key' },
|
|
289
293
|
],
|
|
290
294
|
events: {
|
|
291
295
|
// #473: the attachment surface emits these on upload/remove; declaring them keeps the
|
|
@@ -293,8 +297,11 @@ export const permModManifest = moduleManifest.parse({
|
|
|
293
297
|
emits: [
|
|
294
298
|
{ type: 'attachment.added', schemaVersion: 1 },
|
|
295
299
|
{ type: 'attachment.removed', schemaVersion: 1 },
|
|
300
|
+
{ type: 'perm.check-requested', schemaVersion: 1 },
|
|
296
301
|
],
|
|
297
|
-
|
|
302
|
+
// #1642: a consumer runs under a SYSTEM actor, whose check allows without asking
|
|
303
|
+
// the checker — the one path a malformed key could slip through unrefused.
|
|
304
|
+
consumes: [{ type: 'perm.check-requested', schemaVersion: 1 }],
|
|
298
305
|
},
|
|
299
306
|
migrations: { journalDir: './migrations', compatibleFrom: '1.0.0' },
|
|
300
307
|
// #473: an attachment target on `item`, gated read=perm:read / write=perm:use — reusing
|
|
@@ -604,6 +611,89 @@ const authorizedReadOp = async (ctx, input) => {
|
|
|
604
611
|
return ctx.sql.query('SELECT COUNT(*) AS n FROM testmod_items')[0].n;
|
|
605
612
|
};
|
|
606
613
|
const readOutboxOp = (ctx) => ctx.sql.query('SELECT id, type, authorization, impersonation, operation FROM _substrat_outbox ORDER BY id');
|
|
614
|
+
// #1642: a check whose throw is CAUGHT AND IGNORED — the careless caller a
|
|
615
|
+
// fail-closed refusal has to survive. It reports what it was handed back, then emits
|
|
616
|
+
// anyway, so a test can read what the event's K-34 `authorization` says passed.
|
|
617
|
+
const swallowedCheckOp = async (ctx, input) => {
|
|
618
|
+
let allowed = false;
|
|
619
|
+
let threw = null;
|
|
620
|
+
try {
|
|
621
|
+
allowed = (await ctx.check(input.permission)).allowed;
|
|
622
|
+
}
|
|
623
|
+
catch (err) {
|
|
624
|
+
threw = err instanceof Error ? err.message : String(err);
|
|
625
|
+
}
|
|
626
|
+
ctx.emit({
|
|
627
|
+
type: 'perm.acted',
|
|
628
|
+
schemaVersion: 1,
|
|
629
|
+
entity: { entityType: 'test-thing', entityId: 'swallowed' },
|
|
630
|
+
piiClass: 'none',
|
|
631
|
+
payload: {},
|
|
632
|
+
});
|
|
633
|
+
return { allowed, threw };
|
|
634
|
+
};
|
|
635
|
+
// #1642: hand a key to the system-actor path. The consumer below does the check.
|
|
636
|
+
const requestCheckOp = (ctx, input) => {
|
|
637
|
+
ctx.emit({
|
|
638
|
+
type: 'perm.check-requested',
|
|
639
|
+
schemaVersion: 1,
|
|
640
|
+
entity: { entityType: 'test-thing', entityId: ulid() },
|
|
641
|
+
piiClass: 'none',
|
|
642
|
+
payload: input,
|
|
643
|
+
});
|
|
644
|
+
};
|
|
645
|
+
const CHECK_LOG_DDL = `CREATE TABLE IF NOT EXISTS perm_check_log (
|
|
646
|
+
event_id TEXT PRIMARY KEY, permission TEXT NOT NULL, allowed INTEGER NOT NULL,
|
|
647
|
+
threw TEXT, grant_threw TEXT)`;
|
|
648
|
+
/**
|
|
649
|
+
* Runs as the system actor, whose `ctx.check` is allowed by construction. With
|
|
650
|
+
* `swallow` it catches the check's throw — and a `ctx.grant` of the same key, which
|
|
651
|
+
* the system actor would otherwise write straight into `_substrat_tuples` — and logs
|
|
652
|
+
* the outcome; without it, the throw dead-letters the event.
|
|
653
|
+
*/
|
|
654
|
+
const checkRequestedConsumer = async (ctx, event) => {
|
|
655
|
+
const p = event.payload;
|
|
656
|
+
const permission = p.permission; // the cast under test
|
|
657
|
+
if (!p.swallow) {
|
|
658
|
+
assertAllowed(await ctx.check(permission));
|
|
659
|
+
ctx.sql.exec(CHECK_LOG_DDL);
|
|
660
|
+
ctx.sql.exec('INSERT INTO perm_check_log (event_id, permission, allowed) VALUES (?, ?, 1)', [
|
|
661
|
+
event.id,
|
|
662
|
+
p.permission,
|
|
663
|
+
]);
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
let allowed = false;
|
|
667
|
+
let threw = null;
|
|
668
|
+
let grantThrew = null;
|
|
669
|
+
try {
|
|
670
|
+
allowed = (await ctx.check(permission)).allowed;
|
|
671
|
+
}
|
|
672
|
+
catch (err) {
|
|
673
|
+
threw = err instanceof Error ? err.message : String(err);
|
|
674
|
+
}
|
|
675
|
+
try {
|
|
676
|
+
await ctx.grant(principalId.parse(p.principal), permission, {
|
|
677
|
+
entityType: 'item',
|
|
678
|
+
entityId: 'check-requested',
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
catch (err) {
|
|
682
|
+
grantThrew = err instanceof Error ? err.message : String(err);
|
|
683
|
+
}
|
|
684
|
+
ctx.sql.exec(CHECK_LOG_DDL);
|
|
685
|
+
ctx.sql.exec('INSERT INTO perm_check_log (event_id, permission, allowed, threw, grant_threw) VALUES (?, ?, ?, ?, ?)', [event.id, p.permission, allowed ? 1 : 0, threw, grantThrew]);
|
|
686
|
+
};
|
|
687
|
+
const readCheckLogOp = (ctx) => {
|
|
688
|
+
ctx.sql.exec(CHECK_LOG_DDL);
|
|
689
|
+
return {
|
|
690
|
+
log: ctx.sql.query('SELECT event_id, permission, allowed, threw, grant_threw FROM perm_check_log ORDER BY event_id'),
|
|
691
|
+
deliveries: ctx.sql.query(`SELECT d.event_id, d.error, json_extract(o.payload, '$.permission') AS permission
|
|
692
|
+
FROM _substrat_deliveries d JOIN _substrat_outbox o ON o.id = d.event_id
|
|
693
|
+
WHERE o.type = 'perm.check-requested' ORDER BY d.event_id`),
|
|
694
|
+
tuples: ctx.sql.query(`SELECT subject, relation, object FROM _substrat_tuples WHERE object = 'item:check-requested'`),
|
|
695
|
+
};
|
|
696
|
+
};
|
|
607
697
|
const readDenialsOp = (ctx) => ctx.sql.query(`SELECT actor, permission, tenant_id, scope_id, operation, impersonation, invocation_id
|
|
608
698
|
FROM _substrat_denials ORDER BY id`);
|
|
609
699
|
// -- K-42 fixtures (#868) ----------------------------------------------------
|
|
@@ -1003,6 +1093,10 @@ export const permMod = {
|
|
|
1003
1093
|
'perm/authorized-read': authorizedReadOp,
|
|
1004
1094
|
'perm/read-outbox': readOutboxOp,
|
|
1005
1095
|
'perm/read-denials': readDenialsOp,
|
|
1096
|
+
// #1642
|
|
1097
|
+
'perm/swallowed-check': swallowedCheckOp,
|
|
1098
|
+
'perm/request-check': requestCheckOp,
|
|
1099
|
+
'perm/read-check-log': readCheckLogOp,
|
|
1006
1100
|
// K-42 (#868)
|
|
1007
1101
|
'perm/write-note': noteWriteOp,
|
|
1008
1102
|
'perm/read-notes': noteReadOp,
|
|
@@ -1041,6 +1135,9 @@ export const permMod = {
|
|
|
1041
1135
|
return { revoked: true };
|
|
1042
1136
|
}),
|
|
1043
1137
|
},
|
|
1138
|
+
consumers: {
|
|
1139
|
+
'perm.check-requested': checkRequestedConsumer,
|
|
1140
|
+
},
|
|
1044
1141
|
};
|
|
1045
1142
|
export const connectorModManifest = moduleManifest.parse({
|
|
1046
1143
|
id: '@test/connector',
|
|
@@ -1583,6 +1680,178 @@ export const idempotencyMod = {
|
|
|
1583
1680
|
operationConcurrency: operationConcurrencyOf(idempotencyDeclaration),
|
|
1584
1681
|
operationIdempotencyOptOuts: operationIdempotencyOptOutsOf(idempotencyDeclaration),
|
|
1585
1682
|
};
|
|
1683
|
+
// -- capabilities (#1672) ------------------------------------------------------
|
|
1684
|
+
/**
|
|
1685
|
+
* The fixture for the capability suite: a folder tree (`doc` → `folder`, `folder` →
|
|
1686
|
+
* `folder`), three keys, and operations shaped the way a document vertical's would be —
|
|
1687
|
+
* a read and a write that check ONE entity, so what a capability can reach is decided by
|
|
1688
|
+
* the checker's walk and nothing else.
|
|
1689
|
+
*
|
|
1690
|
+
* The minting operations are deliberately UNGUARDED, like `perm/share`: the guardrails
|
|
1691
|
+
* under test live inside `ctx.capabilities`, and an operation-level check in front of
|
|
1692
|
+
* them would only prove the check.
|
|
1693
|
+
*/
|
|
1694
|
+
export const capModManifest = moduleManifest.parse({
|
|
1695
|
+
id: '@cap/mod',
|
|
1696
|
+
version: '1.0.0',
|
|
1697
|
+
kernelContract: '^0.0.1',
|
|
1698
|
+
permissions: [
|
|
1699
|
+
{ key: 'cap:read', description: 'read a folder or a document' },
|
|
1700
|
+
{ key: 'cap:write', description: 'comment on a document' },
|
|
1701
|
+
{ key: 'cap:admin', description: 'arrange the folder tree' },
|
|
1702
|
+
],
|
|
1703
|
+
events: {
|
|
1704
|
+
emits: [
|
|
1705
|
+
{ type: 'cap.commented', schemaVersion: 1 },
|
|
1706
|
+
{ type: 'cap.mint-requested', schemaVersion: 1 },
|
|
1707
|
+
],
|
|
1708
|
+
consumes: [{ type: 'cap.mint-requested', schemaVersion: 1 }],
|
|
1709
|
+
},
|
|
1710
|
+
migrations: { journalDir: './migrations', compatibleFrom: '1.0.0' },
|
|
1711
|
+
attachmentTargets: [],
|
|
1712
|
+
entityRelations: [
|
|
1713
|
+
{ entityType: 'doc', parentType: 'folder' },
|
|
1714
|
+
{ entityType: 'folder', parentType: 'folder' },
|
|
1715
|
+
],
|
|
1716
|
+
entitlementKey: 'cap',
|
|
1717
|
+
});
|
|
1718
|
+
const CAP_READ = permissionKey.parse('cap:read');
|
|
1719
|
+
const CAP_WRITE = permissionKey.parse('cap:write');
|
|
1720
|
+
/** Every channel through which a module could persist its own minted secret by accident. */
|
|
1721
|
+
export const CAP_LEAK_CHANNELS = ['emit', 'key', 'entity', 'kind', 'intent', 'sql', 'bytes'];
|
|
1722
|
+
export const capMod = {
|
|
1723
|
+
manifest: capModManifest,
|
|
1724
|
+
migrations: [
|
|
1725
|
+
{ version: '0001-init', sql: 'CREATE TABLE cap_notes (id TEXT PRIMARY KEY, body TEXT NOT NULL)' },
|
|
1726
|
+
],
|
|
1727
|
+
operations: {
|
|
1728
|
+
'cap/link': linkOp,
|
|
1729
|
+
'cap/share': ((ctx, input) => ctx.capabilities.mint(input)),
|
|
1730
|
+
// The link a vertical would hand back — the secret spliced into a URL fragment, so the
|
|
1731
|
+
// idempotency redaction has to find it INSIDE a string, not only as a whole value.
|
|
1732
|
+
'cap/share-link': (async (ctx, input) => {
|
|
1733
|
+
const minted = await ctx.capabilities.mint(input);
|
|
1734
|
+
return { id: minted.id, link: `https://docs.example/#share=${minted.secret}` };
|
|
1735
|
+
}),
|
|
1736
|
+
// A module that mints and then writes something through ONE persisting channel. With
|
|
1737
|
+
// `leak: true` what it writes is the secret — each channel an accident could take, and
|
|
1738
|
+
// each must be refused, the refusal rolling the mint back with it. With `leak: false` it
|
|
1739
|
+
// writes a harmless value through the very same channel: the clean twin, which must go
|
|
1740
|
+
// through, so a guard that refused the channel outright would fail it.
|
|
1741
|
+
'cap/share-and-leak': (async (ctx, input) => {
|
|
1742
|
+
const i = input;
|
|
1743
|
+
const { via, leak = true, ...spec } = i;
|
|
1744
|
+
const minted = await ctx.capabilities.mint(spec);
|
|
1745
|
+
const value = leak ? minted.secret : `harmless-${minted.id}`;
|
|
1746
|
+
const note = (payload, entity = spec.entity) => ctx.emit({ type: 'cap.commented', schemaVersion: 1, entity, piiClass: 'none', payload });
|
|
1747
|
+
switch (via) {
|
|
1748
|
+
case 'emit': // a payload value, spliced into a string
|
|
1749
|
+
note({ note: `share it: ${value}` });
|
|
1750
|
+
break;
|
|
1751
|
+
case 'key': // an object KEY — JSON persists property names too
|
|
1752
|
+
note({ links: { [value]: true } });
|
|
1753
|
+
break;
|
|
1754
|
+
case 'entity': // the event's entity id — persisted in its own outbox column
|
|
1755
|
+
note({ note: 'filed under the link' }, { entityType: 'doc', entityId: value });
|
|
1756
|
+
break;
|
|
1757
|
+
case 'kind': // a platform intent's kind — persisted as surely as its payload
|
|
1758
|
+
ctx.requestPlatform({ kind: value, payload: {} });
|
|
1759
|
+
break;
|
|
1760
|
+
case 'intent': // a platform intent's payload
|
|
1761
|
+
ctx.requestPlatform({ kind: 'cap-note', payload: { body: value } });
|
|
1762
|
+
break;
|
|
1763
|
+
case 'sql': // a text SQL parameter
|
|
1764
|
+
ctx.sql.exec('INSERT INTO cap_notes (id, body) VALUES (?, ?)', [ulid(), value]);
|
|
1765
|
+
break;
|
|
1766
|
+
case 'bytes': // a BYTE SQL parameter — stored as a BLOB, still the plaintext
|
|
1767
|
+
ctx.sql.exec('INSERT INTO cap_notes (id, body) VALUES (?, ?)', [
|
|
1768
|
+
ulid(),
|
|
1769
|
+
new TextEncoder().encode(value),
|
|
1770
|
+
]);
|
|
1771
|
+
break;
|
|
1772
|
+
}
|
|
1773
|
+
return { id: minted.id };
|
|
1774
|
+
}),
|
|
1775
|
+
'cap/unshare': (async (ctx, input) => {
|
|
1776
|
+
await ctx.capabilities.revoke(input.id);
|
|
1777
|
+
return { revoked: true };
|
|
1778
|
+
}),
|
|
1779
|
+
'cap/list': ((ctx, input) => ctx.capabilities.list(input)),
|
|
1780
|
+
// A read of ONE entity — the proof comes back so a test can see what authorized it.
|
|
1781
|
+
'cap/read': (async (ctx, input) => {
|
|
1782
|
+
const entity = input.entity;
|
|
1783
|
+
const decision = await ctx.check(CAP_READ, entity);
|
|
1784
|
+
assertAllowed(decision);
|
|
1785
|
+
return { read: entity, proof: decision.proof };
|
|
1786
|
+
}),
|
|
1787
|
+
// A node-level read: a capability holds no node-level authority, so this refuses it.
|
|
1788
|
+
'cap/read-all': (async (ctx) => {
|
|
1789
|
+
assertAllowed(await ctx.check(CAP_READ));
|
|
1790
|
+
return { all: true };
|
|
1791
|
+
}),
|
|
1792
|
+
// A write of ONE entity, which emits — K-34 authorization and the actor land on it.
|
|
1793
|
+
'cap/comment': (async (ctx, input) => {
|
|
1794
|
+
const i = input;
|
|
1795
|
+
assertAllowed(await ctx.check(CAP_WRITE, i.doc));
|
|
1796
|
+
ctx.emit({
|
|
1797
|
+
type: 'cap.commented',
|
|
1798
|
+
schemaVersion: 1,
|
|
1799
|
+
entity: i.doc,
|
|
1800
|
+
piiClass: 'none',
|
|
1801
|
+
payload: { body: i.body },
|
|
1802
|
+
});
|
|
1803
|
+
return { commented: true };
|
|
1804
|
+
}),
|
|
1805
|
+
'cap/whoami': whoAmIOp,
|
|
1806
|
+
// Asks the CONSUMER below to mint. A consumer runs under an override actor whose
|
|
1807
|
+
// checks allow unconditionally, so "the minter holds every key" would be vacuous
|
|
1808
|
+
// there — the mint must be refused outright, whatever the check would have said.
|
|
1809
|
+
'cap/request-mint': (async (ctx, input) => {
|
|
1810
|
+
ctx.emit({
|
|
1811
|
+
type: 'cap.mint-requested',
|
|
1812
|
+
schemaVersion: 1,
|
|
1813
|
+
entity: input.entity,
|
|
1814
|
+
piiClass: 'none',
|
|
1815
|
+
payload: { entity: input.entity },
|
|
1816
|
+
});
|
|
1817
|
+
return { requested: true };
|
|
1818
|
+
}),
|
|
1819
|
+
'cap/notes': ((ctx) => ctx.sql.query('SELECT id, body FROM cap_notes ORDER BY id')),
|
|
1820
|
+
'cap/outbox': ((ctx) => ctx.sql.query('SELECT id, type, occurred_at, actor, authorization, operation, entity_type, entity_id, payload FROM _substrat_outbox ORDER BY id')),
|
|
1821
|
+
'cap/denials': readDenialsOp,
|
|
1822
|
+
// The session table's size — what the bounded prune is measured by.
|
|
1823
|
+
'cap/session-count': ((ctx) => ctx.sql.query('SELECT COUNT(*) AS n FROM _substrat_capability_sessions')[0]?.n ??
|
|
1824
|
+
0),
|
|
1825
|
+
// Every table in this scope, every row, as text — what "the secret appears in no stored
|
|
1826
|
+
// row" is checked against. Read through module-facing `ctx.sql` on purpose: this is
|
|
1827
|
+
// what a vertical (or a dump of the scope) can see.
|
|
1828
|
+
'cap/dump': ((ctx) => {
|
|
1829
|
+
const tables = ctx.sql.query("SELECT name FROM sqlite_master WHERE type = 'table' " +
|
|
1830
|
+
"AND substr(name, 1, 4) <> '_cf_' AND substr(name, 1, 7) <> 'sqlite_'");
|
|
1831
|
+
const out = {};
|
|
1832
|
+
for (const { name } of tables) {
|
|
1833
|
+
out[name] = JSON.stringify(ctx.sql.query(`SELECT * FROM "${name}"`));
|
|
1834
|
+
}
|
|
1835
|
+
return out;
|
|
1836
|
+
}),
|
|
1837
|
+
},
|
|
1838
|
+
consumers: {
|
|
1839
|
+
// Records what the mint answered rather than letting the delivery fail, so the test
|
|
1840
|
+
// reads an outcome instead of inferring one from a dead letter.
|
|
1841
|
+
'cap.mint-requested': (async (ctx, event) => {
|
|
1842
|
+
const entity = event.payload.entity;
|
|
1843
|
+
let outcome;
|
|
1844
|
+
try {
|
|
1845
|
+
const minted = await ctx.capabilities.mint({ entity, permissions: [CAP_READ] });
|
|
1846
|
+
outcome = `minted ${minted.id}`;
|
|
1847
|
+
}
|
|
1848
|
+
catch (err) {
|
|
1849
|
+
outcome = `refused ${err.code ?? err.message}`;
|
|
1850
|
+
}
|
|
1851
|
+
ctx.sql.exec('INSERT INTO cap_notes (id, body) VALUES (?, ?)', [event.id, outcome]);
|
|
1852
|
+
}),
|
|
1853
|
+
},
|
|
1854
|
+
};
|
|
1586
1855
|
export const contractTestModules = [
|
|
1587
1856
|
...contractTestInitialModules,
|
|
1588
1857
|
lateMod,
|
|
@@ -1603,6 +1872,9 @@ export const contractTestModules = [
|
|
|
1603
1872
|
parseMod,
|
|
1604
1873
|
concurrencyMod,
|
|
1605
1874
|
idempotencyMod,
|
|
1875
|
+
// #1672: the capability suite's folder tree. Inert for every other suite — nothing else
|
|
1876
|
+
// reads `cap_notes` or invokes a `cap/*` operation.
|
|
1877
|
+
capMod,
|
|
1606
1878
|
];
|
|
1607
1879
|
// -- live reads (#938) -------------------------------------------------------
|
|
1608
1880
|
/**
|