borgmcp-shared 0.14.0 → 1.0.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/README.md +4 -3
- package/RELEASES.md +7 -0
- package/dist/conformance/adapter.d.ts +9 -2
- package/dist/conformance/adapter.d.ts.map +1 -1
- package/dist/conformance/adapter.js +128 -43
- package/dist/conformance/adapter.js.map +1 -1
- package/dist/conformance/index.d.ts +25 -3
- package/dist/conformance/index.d.ts.map +1 -1
- package/dist/conformance/index.js +73 -18
- package/dist/conformance/index.js.map +1 -1
- package/dist/protocol/contract.d.ts +1 -1
- package/dist/protocol/contract.d.ts.map +1 -1
- package/dist/protocol/contract.js +13 -18
- package/dist/protocol/contract.js.map +1 -1
- package/dist/protocol/coordination.d.ts +10 -0
- package/dist/protocol/coordination.d.ts.map +1 -1
- package/dist/protocol/coordination.js +20 -6
- package/dist/protocol/coordination.js.map +1 -1
- package/dist/protocol/errors.d.ts +1 -1
- package/dist/protocol/errors.d.ts.map +1 -1
- package/dist/protocol/errors.js +1 -1
- package/dist/protocol/errors.js.map +1 -1
- package/dist/protocol/types.d.ts +1 -5
- package/dist/protocol/types.d.ts.map +1 -1
- package/dist/protocol/version.d.ts +1 -1
- package/dist/protocol/version.js +1 -1
- package/dist/templates.d.ts +1 -3
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +7 -79
- package/dist/templates.js.map +1 -1
- package/docs/compatibility.md +9 -0
- package/docs/release-records.json +14 -0
- package/docs/releases/1.0.0.md +11 -0
- package/docs/template-lifecycle.md +1 -1
- package/package.json +1 -1
- package/src/conformance/adapter.ts +232 -52
- package/src/conformance/index.ts +88 -22
- package/src/protocol/contract.ts +15 -21
- package/src/protocol/coordination.ts +36 -7
- package/src/protocol/errors.ts +1 -1
- package/src/protocol/types.ts +1 -5
- package/src/protocol/version.ts +2 -2
- package/src/templates.ts +7 -81
package/docs/compatibility.md
CHANGED
|
@@ -97,6 +97,15 @@ per-recipient acknowledgement timestamps and separate advisory claims. A v10
|
|
|
97
97
|
peer and a v11 peer reject each other during credential-free preflight before
|
|
98
98
|
either peer dispatches the new query.
|
|
99
99
|
|
|
100
|
+
`borgmcp-shared@1.0.0` carries protocol v12. It adds cursorless exact-entry
|
|
101
|
+
lookup by canonical UUID or unique eight-hex prefix, with typed unknown and
|
|
102
|
+
ambiguous-prefix refusals. Every append request must also choose exactly one
|
|
103
|
+
explicit audience through `to: "broadcast"` or a non-empty recipient-selector
|
|
104
|
+
array. The public append request has no `visibility` or `recipientDroneIds`,
|
|
105
|
+
message taxonomy classifies prefixes and lifecycle only, and routing never falls
|
|
106
|
+
open. A v11 peer and a v12 peer reject each other during credential-free
|
|
107
|
+
preflight before either new contract is dispatched.
|
|
108
|
+
|
|
100
109
|
Removing or reinterpreting an existing field is a protocol-breaking change even
|
|
101
110
|
when TypeScript permits it. Implementations must not infer compatibility from a
|
|
102
111
|
successful build alone — they must run the shared conformance suite against the
|
|
@@ -229,5 +229,19 @@
|
|
|
229
229
|
"verify_job_id": null,
|
|
230
230
|
"publish_job_id": null,
|
|
231
231
|
"artifact_integrity": "sha512-AT5xOpd99U1Xd2waRJs01i2fz9ApMzd+NXQh0WMwTUknSsHpaQaEtGjcxtEDZG6MbB6nWfhoWG1ZZm9Fdkoytg=="
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
"outcome": "published",
|
|
235
|
+
"version": "0.14.0",
|
|
236
|
+
"tag": "v0.14.0",
|
|
237
|
+
"tag_object": "4ecd4e54bbf5a3dd719855ccc4d6f9db3939800f",
|
|
238
|
+
"commit": "a69926023d53268131e95b63efd141a17c634e2d",
|
|
239
|
+
"tree": "9074b05b687178fadc6d7cb40e55d8e5cf9cf248",
|
|
240
|
+
"workflow_run_id": 31957839605,
|
|
241
|
+
"workflow_run_attempt": 1,
|
|
242
|
+
"workflow_conclusion": "success",
|
|
243
|
+
"verify_job_id": null,
|
|
244
|
+
"publish_job_id": null,
|
|
245
|
+
"artifact_integrity": "sha512-rKRMxnxTnW+o3WOqtAFoih9IeRKCZw/lltqz7CbjC1riNq52CsZ871o1Fo7Fdk0WqwuzdFQmRJ7qOzPi08Q/QA=="
|
|
232
246
|
}
|
|
233
247
|
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
This major release introduces the protocol v12 trustworthy-messaging contracts
|
|
2
|
+
used by the matching Borg MCP server and client major releases.
|
|
3
|
+
|
|
4
|
+
- Every append requires an explicit audience: scalar `"broadcast"` or a non-empty array of recipient selectors.
|
|
5
|
+
- Omitted, null, empty, malformed, and retired addressing inputs refuse before persistence; classification never supplies an audience or falls open.
|
|
6
|
+
- Public append requests no longer expose `visibility` or `recipientDroneIds`; stored and read entries retain resolved visibility and recipient IDs.
|
|
7
|
+
- Message taxonomy retains class, prefixes, and lifecycle only. Every bundled template and playbook requires explicit `to`, including `to: "broadcast"` for cube-wide messages.
|
|
8
|
+
- Routing echoes contain only the resolved class and recipients.
|
|
9
|
+
- Cursorless entry lookup accepts a canonical full UUID or one unique eight-hex prefix and returns the complete enriched log entry.
|
|
10
|
+
- Unknown or inaccessible entries return `NOT_FOUND`; ambiguous prefixes return `LOG_ENTRY_PREFIX_AMBIGUOUS` without selecting an arbitrary match.
|
|
11
|
+
- Entry lookup conformance proves no unread, acknowledgement, claim, log, or authority-state mutation.
|
|
@@ -48,7 +48,7 @@ Existing cubes are snapshots with local ownership, not replicas. They do not aut
|
|
|
48
48
|
|
|
49
49
|
Use the existing focused controls; do not add a second template lifecycle mechanism:
|
|
50
50
|
|
|
51
|
-
- `test/templates.test.ts` pins the built-in set, expected software-development roles and flags, host-neutral presentation copy, authority/scope language, role-size budgets, Builder minimum-sufficient-change ordering, taxonomy
|
|
51
|
+
- `test/templates.test.ts` pins the built-in set, expected software-development roles and flags, host-neutral presentation copy, authority/scope language, role-size budgets, Builder minimum-sufficient-change ordering, classification-only taxonomy, explicit log addressing, generated/source equality, and no-clobber resolution.
|
|
52
52
|
- `test/role-section.test.ts` pins plain-label parsing, byte-identical parse/serialize for real template roles, and single-section replacement/insertion/deletion without clobbering unrelated text.
|
|
53
53
|
- `test/packed-artifact.test.ts` installs the packed package and verifies the public named-template creation contract and shipped role text from the consumer surface.
|
|
54
54
|
- `CUBE_TEMPLATE_ACCEPTANCE_CONFORMANCE` pins the closed accepted template-name set and invalid-name rejection. Stateful create vectors pin template identity to retry behavior.
|
package/package.json
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
decodeDeleteCubeResponseEnvelope,
|
|
8
8
|
decodeAppendLogResultEnvelope,
|
|
9
9
|
decodeAckStatusResultEnvelope,
|
|
10
|
+
decodeEntryQueryResultEnvelope,
|
|
10
11
|
decodeDecisionResultEnvelope,
|
|
11
12
|
decodeDecisionsResultEnvelope,
|
|
12
13
|
decodeEnrollmentExchangeResponseEnvelope,
|
|
@@ -41,6 +42,7 @@ import {
|
|
|
41
42
|
type StreamEvent,
|
|
42
43
|
type DroneRuntimeMetadata,
|
|
43
44
|
type CubeDocument,
|
|
45
|
+
type EnrichedStreamEntry,
|
|
44
46
|
} from '../protocol/index.js';
|
|
45
47
|
import {
|
|
46
48
|
APPEND_LOG_IDEMPOTENCY_CONFORMANCE,
|
|
@@ -122,6 +124,7 @@ export interface ConformanceAuthorityState {
|
|
|
122
124
|
enrollment_claims: number;
|
|
123
125
|
activity_acknowledgements: number;
|
|
124
126
|
activity_claims: number;
|
|
127
|
+
activity_log_entries: number;
|
|
125
128
|
cubes: number;
|
|
126
129
|
roles: number;
|
|
127
130
|
grants: number;
|
|
@@ -217,12 +220,7 @@ export interface ConformanceAdmin {
|
|
|
217
220
|
readonly isDefault?: boolean;
|
|
218
221
|
readonly isMandatory?: boolean;
|
|
219
222
|
}): Promise<ConformanceRole>;
|
|
220
|
-
|
|
221
|
-
configureMessageClassRouting(
|
|
222
|
-
cube: ConformanceCube,
|
|
223
|
-
className: string,
|
|
224
|
-
recipientDroneIds: readonly string[],
|
|
225
|
-
): Promise<void>;
|
|
223
|
+
replaceLogEntryId(cube: ConformanceCube, currentId: string, replacementId: string): Promise<void>;
|
|
226
224
|
createDrone(
|
|
227
225
|
principal: ConformancePrincipal,
|
|
228
226
|
cube: ConformanceCube,
|
|
@@ -302,6 +300,11 @@ export interface ConformanceOperations {
|
|
|
302
300
|
cube: ConformanceCube,
|
|
303
301
|
request: unknown,
|
|
304
302
|
): Promise<ConformanceHttpResponse>;
|
|
303
|
+
entryQuery(
|
|
304
|
+
credential: string,
|
|
305
|
+
cube: ConformanceCube,
|
|
306
|
+
request: unknown,
|
|
307
|
+
): Promise<ConformanceHttpResponse>;
|
|
305
308
|
ack(
|
|
306
309
|
credential: string,
|
|
307
310
|
cube: ConformanceCube,
|
|
@@ -387,6 +390,8 @@ export const ADAPTER_CONFORMANCE_FIXTURES = [
|
|
|
387
390
|
{ id: 'security.oversize-request', area: 'security' },
|
|
388
391
|
{ id: 'security.cross-cube-isolation', area: 'security' },
|
|
389
392
|
{ id: 'log.append-idempotency', area: 'log' },
|
|
393
|
+
{ id: 'log.mandatory-addressing', area: 'log' },
|
|
394
|
+
{ id: 'log.entry-query', area: 'log' },
|
|
390
395
|
{ id: 'log.read-cursor-tuple', area: 'cursor' },
|
|
391
396
|
{ id: 'sse.replay-live-transition', area: 'sse' },
|
|
392
397
|
{ id: 'cursor.explicit-expiry', area: 'cursor' },
|
|
@@ -1477,6 +1482,7 @@ export async function runAdapterConformance(
|
|
|
1477
1482
|
documentCube,
|
|
1478
1483
|
createProtocolEnvelope('document-foreign-citation', {
|
|
1479
1484
|
post_id: '00000000-0000-4000-8000-00000000031f',
|
|
1485
|
+
to: 'broadcast',
|
|
1480
1486
|
message: 'Foreign evidence must remain hidden.',
|
|
1481
1487
|
documents: [foreignDocument.id],
|
|
1482
1488
|
}),
|
|
@@ -1518,6 +1524,7 @@ export async function runAdapterConformance(
|
|
|
1518
1524
|
documentCube,
|
|
1519
1525
|
createProtocolEnvelope('document-citation', {
|
|
1520
1526
|
post_id: '00000000-0000-4000-8000-000000000320',
|
|
1527
|
+
to: 'broadcast',
|
|
1521
1528
|
message: 'See the document evidence.',
|
|
1522
1529
|
documents: [created.id, successor.id],
|
|
1523
1530
|
}),
|
|
@@ -1530,6 +1537,7 @@ export async function runAdapterConformance(
|
|
|
1530
1537
|
documentCube,
|
|
1531
1538
|
createProtocolEnvelope('document-citation-conflict', {
|
|
1532
1539
|
post_id: '00000000-0000-4000-8000-000000000320',
|
|
1540
|
+
to: 'broadcast',
|
|
1533
1541
|
message: 'See the document evidence.',
|
|
1534
1542
|
documents: [successor.id],
|
|
1535
1543
|
}),
|
|
@@ -1637,14 +1645,14 @@ export async function runAdapterConformance(
|
|
|
1637
1645
|
documentCredential,
|
|
1638
1646
|
documentCube,
|
|
1639
1647
|
createProtocolEnvelope('document-unknown-citation', {
|
|
1640
|
-
post_id: '00000000-0000-4000-8000-000000000321', message: 'Unknown.', documents: ['unknown_full_id'],
|
|
1648
|
+
post_id: '00000000-0000-4000-8000-000000000321', message: 'Unknown.', to: 'broadcast', documents: ['unknown_full_id'],
|
|
1641
1649
|
}),
|
|
1642
1650
|
), 404, ErrorCode.DOCUMENT_NOT_FOUND, 'Unknown document citation');
|
|
1643
1651
|
const longAccepted = await environment.operations.append(
|
|
1644
1652
|
documentCredential,
|
|
1645
1653
|
documentCube,
|
|
1646
1654
|
createProtocolEnvelope('document-advisory', {
|
|
1647
|
-
post_id: '00000000-0000-4000-8000-000000000322', message: 'x'.repeat(1025),
|
|
1655
|
+
post_id: '00000000-0000-4000-8000-000000000322', message: 'x'.repeat(1025), to: 'broadcast',
|
|
1648
1656
|
}),
|
|
1649
1657
|
);
|
|
1650
1658
|
expectStatus(longAccepted, 201, 'Advisory log append');
|
|
@@ -1653,7 +1661,7 @@ export async function runAdapterConformance(
|
|
|
1653
1661
|
documentCredential,
|
|
1654
1662
|
documentCube,
|
|
1655
1663
|
createProtocolEnvelope('document-hard-cap', {
|
|
1656
|
-
post_id: '00000000-0000-4000-8000-000000000323', message: 'x'.repeat(4097),
|
|
1664
|
+
post_id: '00000000-0000-4000-8000-000000000323', message: 'x'.repeat(4097), to: 'broadcast',
|
|
1657
1665
|
}),
|
|
1658
1666
|
), 413, ErrorCode.CONTENT_TOO_LARGE, 'Hard-cap log append');
|
|
1659
1667
|
const citedRead = decodeReadLogResultEnvelope((await environment.operations.read(
|
|
@@ -1680,6 +1688,7 @@ export async function runAdapterConformance(
|
|
|
1680
1688
|
const injectedBody = JSON.stringify(
|
|
1681
1689
|
createProtocolEnvelope('inject-b1', {
|
|
1682
1690
|
post_id: '00000000-0000-4000-8000-000000000301',
|
|
1691
|
+
to: 'broadcast',
|
|
1683
1692
|
message: injectedMessage,
|
|
1684
1693
|
}),
|
|
1685
1694
|
);
|
|
@@ -1702,6 +1711,7 @@ export async function runAdapterConformance(
|
|
|
1702
1711
|
cubeB,
|
|
1703
1712
|
createProtocolEnvelope('inject-b2', {
|
|
1704
1713
|
post_id: '00000000-0000-4000-8000-000000000302',
|
|
1714
|
+
to: 'broadcast',
|
|
1705
1715
|
message: 'post-injection-sentinel',
|
|
1706
1716
|
}),
|
|
1707
1717
|
);
|
|
@@ -1752,6 +1762,7 @@ export async function runAdapterConformance(
|
|
|
1752
1762
|
const baseBody = JSON.stringify(
|
|
1753
1763
|
createProtocolEnvelope('oversize-a1', {
|
|
1754
1764
|
post_id: '00000000-0000-4000-8000-000000000303',
|
|
1765
|
+
to: 'broadcast',
|
|
1755
1766
|
message: 'must-not-persist',
|
|
1756
1767
|
}),
|
|
1757
1768
|
);
|
|
@@ -1784,6 +1795,7 @@ export async function runAdapterConformance(
|
|
|
1784
1795
|
createProtocolEnvelope('append-b1', {
|
|
1785
1796
|
post_id: '00000000-0000-4000-8000-000000000304',
|
|
1786
1797
|
message: 'principal-b-secret',
|
|
1798
|
+
to: 'broadcast',
|
|
1787
1799
|
}),
|
|
1788
1800
|
);
|
|
1789
1801
|
expectStatus(secretAppend, 201, 'Principal B append');
|
|
@@ -1810,16 +1822,12 @@ export async function runAdapterConformance(
|
|
|
1810
1822
|
});
|
|
1811
1823
|
const firstRecipient = await environment.admin.createDrone(principal, cube, role);
|
|
1812
1824
|
const secondRecipient = await environment.admin.createDrone(principal, cube, role);
|
|
1813
|
-
await environment.admin.configureMessageClassRouting(cube, 'conformance-review', [firstRecipient.id]);
|
|
1814
|
-
await environment.admin.configureMessageClassRouting(cube, 'conformance-security', [firstRecipient.id]);
|
|
1815
1825
|
const postId = '00000000-0000-4000-8000-000000000313';
|
|
1816
1826
|
const initialPayload = {
|
|
1817
1827
|
post_id: postId,
|
|
1818
1828
|
message: 'once',
|
|
1819
|
-
|
|
1820
|
-
recipientDroneIds: [firstRecipient.id],
|
|
1829
|
+
to: [firstRecipient.id],
|
|
1821
1830
|
class: 'review',
|
|
1822
|
-
to: ['Coordinator'],
|
|
1823
1831
|
};
|
|
1824
1832
|
const first = await environment.operations.append(
|
|
1825
1833
|
credential,
|
|
@@ -1830,32 +1838,12 @@ export async function runAdapterConformance(
|
|
|
1830
1838
|
const firstResult = decodeAppendLogResultEnvelope(first.body).payload;
|
|
1831
1839
|
invariant(!firstResult.deduplicated, 'Initial append was reported as deduplicated.');
|
|
1832
1840
|
for (const [index, vector] of APPEND_LOG_IDEMPOTENCY_CONFORMANCE.entries()) {
|
|
1833
|
-
const
|
|
1834
|
-
const vectorPostId = classRouting
|
|
1835
|
-
? '00000000-0000-4000-8000-000000000314'
|
|
1836
|
-
: postId;
|
|
1837
|
-
if (classRouting) {
|
|
1838
|
-
const baseline = await environment.operations.append(
|
|
1839
|
-
credential,
|
|
1840
|
-
cube,
|
|
1841
|
-
createProtocolEnvelope('append-idempotency-class-baseline', {
|
|
1842
|
-
post_id: vectorPostId,
|
|
1843
|
-
message: 'once',
|
|
1844
|
-
class: 'conformance-review',
|
|
1845
|
-
}),
|
|
1846
|
-
);
|
|
1847
|
-
expectStatus(baseline, 201, 'Resolved class-routing baseline');
|
|
1848
|
-
}
|
|
1849
|
-
const payload = classRouting ? {
|
|
1850
|
-
post_id: vectorPostId,
|
|
1851
|
-
message: 'once',
|
|
1852
|
-
class: 'conformance-security',
|
|
1853
|
-
} : {
|
|
1841
|
+
const payload = {
|
|
1854
1842
|
...initialPayload,
|
|
1855
1843
|
...(vector.mutation === 'message' ? { message: 'changed' } : {}),
|
|
1856
|
-
...(vector.mutation === '
|
|
1857
|
-
...(vector.mutation === 'recipient_set' ? {
|
|
1858
|
-
...(vector.mutation === '
|
|
1844
|
+
...(vector.mutation === 'audience' ? { to: 'broadcast' as const } : {}),
|
|
1845
|
+
...(vector.mutation === 'recipient_set' ? { to: [secondRecipient.id] } : {}),
|
|
1846
|
+
...(vector.mutation === 'classification' ? { class: 'security' } : {}),
|
|
1859
1847
|
};
|
|
1860
1848
|
const response = await environment.operations.append(
|
|
1861
1849
|
vector.actor === 'same' ? credential : otherCredential,
|
|
@@ -1883,10 +1871,207 @@ export async function runAdapterConformance(
|
|
|
1883
1871
|
);
|
|
1884
1872
|
expectStatus(read, 200, 'Idempotent append read');
|
|
1885
1873
|
invariant(
|
|
1886
|
-
decodeReadLogResultEnvelope(read.body).payload.entries.length ===
|
|
1874
|
+
decodeReadLogResultEnvelope(read.body).payload.entries.length === 2,
|
|
1887
1875
|
'Append collision controls persisted the wrong number of entries.',
|
|
1888
1876
|
);
|
|
1889
|
-
return { persisted_entries:
|
|
1877
|
+
return { persisted_entries: 2, stable_entry: true, deduplicated: true, conflicts: 3, cross_author_created: true };
|
|
1878
|
+
});
|
|
1879
|
+
|
|
1880
|
+
await record('log.mandatory-addressing', async () => {
|
|
1881
|
+
const cube = await environment.admin.createCube('mandatory-addressing-cube');
|
|
1882
|
+
await environment.admin.grantCube(principalA, cube);
|
|
1883
|
+
const role = await environment.admin.createRole(cube, {
|
|
1884
|
+
roleClass: 'worker',
|
|
1885
|
+
isHumanSeat: false,
|
|
1886
|
+
name: 'Structured Recipient',
|
|
1887
|
+
});
|
|
1888
|
+
const recipient = await environment.admin.createDrone(principalA, cube, role);
|
|
1889
|
+
const message = 'START NOW exact slice';
|
|
1890
|
+
const before = await environment.admin.observeAuthorityState();
|
|
1891
|
+
expectError(
|
|
1892
|
+
await environment.operations.append(
|
|
1893
|
+
credentialA,
|
|
1894
|
+
cube,
|
|
1895
|
+
createProtocolEnvelope('mandatory-addressing-omitted', {
|
|
1896
|
+
post_id: '30000000-0000-4000-8000-000000000001',
|
|
1897
|
+
message,
|
|
1898
|
+
}),
|
|
1899
|
+
),
|
|
1900
|
+
400,
|
|
1901
|
+
ErrorCode.INVALID_INPUT,
|
|
1902
|
+
'Omitted mandatory addressing',
|
|
1903
|
+
);
|
|
1904
|
+
assertStateDelta(before, await environment.admin.observeAuthorityState(), {}, 'Mandatory-addressing refusal');
|
|
1905
|
+
|
|
1906
|
+
const broadcast = await environment.operations.append(
|
|
1907
|
+
credentialA,
|
|
1908
|
+
cube,
|
|
1909
|
+
createProtocolEnvelope('mandatory-addressing-broadcast', {
|
|
1910
|
+
post_id: '30000000-0000-4000-8000-000000000002',
|
|
1911
|
+
message,
|
|
1912
|
+
to: 'broadcast',
|
|
1913
|
+
}),
|
|
1914
|
+
);
|
|
1915
|
+
expectStatus(broadcast, 201, 'Explicit broadcast addressing');
|
|
1916
|
+
const broadcastResult = decodeAppendLogResultEnvelope(broadcast.body).payload;
|
|
1917
|
+
invariant(broadcastResult.entry.visibility === 'broadcast', 'Scalar broadcast did not produce broadcast visibility.');
|
|
1918
|
+
invariant(
|
|
1919
|
+
(broadcastResult.entry.recipient_drone_ids?.length ?? 0) === 0,
|
|
1920
|
+
'Scalar broadcast unexpectedly resolved direct recipients.',
|
|
1921
|
+
);
|
|
1922
|
+
const directed = await environment.operations.append(
|
|
1923
|
+
credentialA,
|
|
1924
|
+
cube,
|
|
1925
|
+
createProtocolEnvelope('mandatory-addressing-directed', {
|
|
1926
|
+
post_id: '30000000-0000-4000-8000-000000000003',
|
|
1927
|
+
message,
|
|
1928
|
+
to: [recipient.id],
|
|
1929
|
+
class: 'cube-wide',
|
|
1930
|
+
}),
|
|
1931
|
+
);
|
|
1932
|
+
expectStatus(directed, 201, 'Recipient-selector union branch');
|
|
1933
|
+
const directedResult = decodeAppendLogResultEnvelope(directed.body).payload;
|
|
1934
|
+
invariant(directedResult.entry.visibility === 'direct', 'Explicit recipient selectors did not produce direct visibility.');
|
|
1935
|
+
invariant(
|
|
1936
|
+
directedResult.entry.recipient_drone_ids?.includes(recipient.id) === true,
|
|
1937
|
+
'Explicit recipient selectors did not resolve the named recipient.',
|
|
1938
|
+
);
|
|
1939
|
+
invariant(
|
|
1940
|
+
directedResult.entry.recipient_drone_ids?.length === 1,
|
|
1941
|
+
'Classification changed or fell open the explicit recipient set.',
|
|
1942
|
+
);
|
|
1943
|
+
return { refused_status: 400, code: ErrorCode.INVALID_INPUT, persisted: false, broadcast: true, union: true, no_fall_open: true };
|
|
1944
|
+
});
|
|
1945
|
+
|
|
1946
|
+
await record('log.entry-query', async () => {
|
|
1947
|
+
const cube = await environment.admin.createCube('entry-query-cube');
|
|
1948
|
+
await environment.admin.grantCube(principalA, cube);
|
|
1949
|
+
const role = await environment.admin.createRole(cube, {
|
|
1950
|
+
roleClass: 'worker',
|
|
1951
|
+
isHumanSeat: false,
|
|
1952
|
+
name: 'Entry Query Worker',
|
|
1953
|
+
});
|
|
1954
|
+
const acknowledgedRecipient = await environment.admin.createDrone(principalA, cube, role);
|
|
1955
|
+
const claimingRecipient = await environment.admin.createDrone(principalA, cube, role);
|
|
1956
|
+
const acknowledgedCredential = await environment.admin.issueManagedDroneSession(acknowledgedRecipient);
|
|
1957
|
+
const claimingCredential = await environment.admin.issueManagedDroneSession(claimingRecipient);
|
|
1958
|
+
const ids = [
|
|
1959
|
+
'11111111-0000-4000-8000-000000000001',
|
|
1960
|
+
'22222222-0000-4000-8000-000000000001',
|
|
1961
|
+
'22222222-0000-4000-8000-000000000002',
|
|
1962
|
+
];
|
|
1963
|
+
const entries: EnrichedStreamEntry[] = [];
|
|
1964
|
+
for (const [index, id] of ids.entries()) {
|
|
1965
|
+
const response = await environment.operations.append(
|
|
1966
|
+
credentialA,
|
|
1967
|
+
cube,
|
|
1968
|
+
createProtocolEnvelope(`entry-query-append-${index}`, {
|
|
1969
|
+
post_id: `40000000-0000-4000-8000-00000000000${index}`,
|
|
1970
|
+
to: [acknowledgedRecipient.id, claimingRecipient.id],
|
|
1971
|
+
message: `entry-query-${index}`,
|
|
1972
|
+
}),
|
|
1973
|
+
);
|
|
1974
|
+
expectStatus(response, 201, `Entry-query fixture append ${index}`);
|
|
1975
|
+
const entry = decodeAppendLogResultEnvelope(response.body).payload.entry;
|
|
1976
|
+
await environment.admin.replaceLogEntryId(cube, entry.id, id);
|
|
1977
|
+
entries.push({ ...entry, id });
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
expectStatus(
|
|
1981
|
+
await environment.operations.ack(
|
|
1982
|
+
acknowledgedCredential,
|
|
1983
|
+
cube,
|
|
1984
|
+
createProtocolEnvelope('entry-query-seed-ack', { entry_id: ids[0], kind: 'ack' }),
|
|
1985
|
+
),
|
|
1986
|
+
204,
|
|
1987
|
+
'Entry-query acknowledgement seed',
|
|
1988
|
+
);
|
|
1989
|
+
expectStatus(
|
|
1990
|
+
await environment.operations.ack(
|
|
1991
|
+
claimingCredential,
|
|
1992
|
+
cube,
|
|
1993
|
+
createProtocolEnvelope('entry-query-seed-claim', { entry_id: ids[1], kind: 'claim' }),
|
|
1994
|
+
),
|
|
1995
|
+
204,
|
|
1996
|
+
'Entry-query claim seed',
|
|
1997
|
+
);
|
|
1998
|
+
|
|
1999
|
+
const before = await environment.admin.observeAuthorityState();
|
|
2000
|
+
for (const [requestId, selector, expectedId] of [
|
|
2001
|
+
['entry-query-full', ids[1], ids[1]],
|
|
2002
|
+
['entry-query-prefix', '11111111', ids[0]],
|
|
2003
|
+
] as const) {
|
|
2004
|
+
const response = await environment.operations.entryQuery(
|
|
2005
|
+
credentialA,
|
|
2006
|
+
cube,
|
|
2007
|
+
createProtocolEnvelope(requestId, { entry_id: selector }),
|
|
2008
|
+
);
|
|
2009
|
+
expectStatus(response, 200, requestId);
|
|
2010
|
+
const result = decodeEntryQueryResultEnvelope(response.body).payload;
|
|
2011
|
+
invariant(result.entry.id === expectedId, `${requestId} returned the wrong entry.`);
|
|
2012
|
+
}
|
|
2013
|
+
expectError(
|
|
2014
|
+
await environment.operations.entryQuery(
|
|
2015
|
+
credentialA,
|
|
2016
|
+
cube,
|
|
2017
|
+
createProtocolEnvelope('entry-query-unknown', { entry_id: 'ffffffff' }),
|
|
2018
|
+
),
|
|
2019
|
+
404,
|
|
2020
|
+
ErrorCode.NOT_FOUND,
|
|
2021
|
+
'Unknown entry query',
|
|
2022
|
+
);
|
|
2023
|
+
expectError(
|
|
2024
|
+
await environment.operations.entryQuery(
|
|
2025
|
+
credentialA,
|
|
2026
|
+
cube,
|
|
2027
|
+
createProtocolEnvelope('entry-query-ambiguous', { entry_id: '22222222' }),
|
|
2028
|
+
),
|
|
2029
|
+
409,
|
|
2030
|
+
ErrorCode.LOG_ENTRY_PREFIX_AMBIGUOUS,
|
|
2031
|
+
'Ambiguous entry query',
|
|
2032
|
+
);
|
|
2033
|
+
assertStateDelta(before, await environment.admin.observeAuthorityState(), {}, 'Entry query');
|
|
2034
|
+
const acknowledgementStatus = decodeAckStatusResultEnvelope((await environment.operations.ackStatus(
|
|
2035
|
+
credentialA,
|
|
2036
|
+
cube,
|
|
2037
|
+
createProtocolEnvelope('entry-query-ack-status', { entry_id: ids[0] }),
|
|
2038
|
+
)).body).payload;
|
|
2039
|
+
const claimStatus = decodeAckStatusResultEnvelope((await environment.operations.ackStatus(
|
|
2040
|
+
credentialA,
|
|
2041
|
+
cube,
|
|
2042
|
+
createProtocolEnvelope('entry-query-claim-status', { entry_id: ids[1] }),
|
|
2043
|
+
)).body).payload;
|
|
2044
|
+
invariant(
|
|
2045
|
+
acknowledgementStatus.recipients.some((recipient) =>
|
|
2046
|
+
recipient.drone_id === acknowledgedRecipient.id && recipient.acknowledged_at !== null
|
|
2047
|
+
),
|
|
2048
|
+
'Entry query removed the seeded acknowledgement.',
|
|
2049
|
+
);
|
|
2050
|
+
invariant(
|
|
2051
|
+
claimStatus.claims.some((claim) => claim.drone_id === claimingRecipient.id),
|
|
2052
|
+
'Entry query removed the seeded claim.',
|
|
2053
|
+
);
|
|
2054
|
+
const unreadResponse = await environment.operations.read(
|
|
2055
|
+
credentialA,
|
|
2056
|
+
cube,
|
|
2057
|
+
createProtocolEnvelope('entry-query-unread', { cursor: null, limit: 10 }),
|
|
2058
|
+
);
|
|
2059
|
+
expectStatus(unreadResponse, 200, 'Entry-query unread read');
|
|
2060
|
+
const unread = decodeReadLogResultEnvelope(unreadResponse.body).payload;
|
|
2061
|
+
invariant(
|
|
2062
|
+
unread.entries.length === entries.length && entries.every((entry) => unread.entries.some((item) => item.id === entry.id)),
|
|
2063
|
+
'Entry query consumed or advanced unread activity.',
|
|
2064
|
+
);
|
|
2065
|
+
return {
|
|
2066
|
+
full_uuid: true,
|
|
2067
|
+
unique_prefix: true,
|
|
2068
|
+
ambiguous_code: ErrorCode.LOG_ENTRY_PREFIX_AMBIGUOUS,
|
|
2069
|
+
unknown_code: ErrorCode.NOT_FOUND,
|
|
2070
|
+
unread_entry_available: true,
|
|
2071
|
+
acknowledgement_preserved: true,
|
|
2072
|
+
claim_preserved: true,
|
|
2073
|
+
mutation: 'none',
|
|
2074
|
+
};
|
|
1890
2075
|
});
|
|
1891
2076
|
|
|
1892
2077
|
const entries: Array<{ id: string; created_at: string; message: string }> = [];
|
|
@@ -1898,6 +2083,7 @@ export async function runAdapterConformance(
|
|
|
1898
2083
|
cubeA,
|
|
1899
2084
|
createProtocolEnvelope(`append-a${index + 1}`, {
|
|
1900
2085
|
post_id: `00000000-0000-4000-8000-${String(305 + index).padStart(12, '0')}`,
|
|
2086
|
+
to: 'broadcast',
|
|
1901
2087
|
message,
|
|
1902
2088
|
}),
|
|
1903
2089
|
);
|
|
@@ -1935,6 +2121,7 @@ export async function runAdapterConformance(
|
|
|
1935
2121
|
cubeA,
|
|
1936
2122
|
createProtocolEnvelope('append-a4', {
|
|
1937
2123
|
post_id: '00000000-0000-4000-8000-000000000308',
|
|
2124
|
+
to: 'broadcast',
|
|
1938
2125
|
message: 'delta',
|
|
1939
2126
|
}),
|
|
1940
2127
|
);
|
|
@@ -1962,6 +2149,7 @@ export async function runAdapterConformance(
|
|
|
1962
2149
|
cubeA,
|
|
1963
2150
|
createProtocolEnvelope('append-a5', {
|
|
1964
2151
|
post_id: '00000000-0000-4000-8000-000000000309',
|
|
2152
|
+
to: 'broadcast',
|
|
1965
2153
|
message: 'epsilon',
|
|
1966
2154
|
}),
|
|
1967
2155
|
);
|
|
@@ -2020,9 +2208,8 @@ export async function runAdapterConformance(
|
|
|
2020
2208
|
cube,
|
|
2021
2209
|
createProtocolEnvelope('ack-status-append', {
|
|
2022
2210
|
post_id: '00000000-0000-4000-8000-000000000310',
|
|
2211
|
+
to: [acknowledgedRecipient.id, claimingRecipient.id],
|
|
2023
2212
|
message: 'START NOW acknowledgement status fixture',
|
|
2024
|
-
visibility: 'direct',
|
|
2025
|
-
recipientDroneIds: [acknowledgedRecipient.id, claimingRecipient.id],
|
|
2026
2213
|
}),
|
|
2027
2214
|
);
|
|
2028
2215
|
expectStatus(append, 201, 'Acknowledgement-status fixture append');
|
|
@@ -2614,8 +2801,7 @@ export async function runAdapterConformance(
|
|
|
2614
2801
|
createProtocolEnvelope('evict-direct-target', {
|
|
2615
2802
|
message: 'must-not-fan-out',
|
|
2616
2803
|
post_id: '00000000-0000-4000-8000-000000000310',
|
|
2617
|
-
|
|
2618
|
-
recipientDroneIds: [evictedDrone.id],
|
|
2804
|
+
to: [evictedDrone.id],
|
|
2619
2805
|
}),
|
|
2620
2806
|
),
|
|
2621
2807
|
404,
|
|
@@ -2695,12 +2881,6 @@ export async function runAdapterConformance(
|
|
|
2695
2881
|
isHumanSeat: true,
|
|
2696
2882
|
name: 'Human Seat',
|
|
2697
2883
|
});
|
|
2698
|
-
const referencedRole = await environment.admin.createRole(roleContractCube, {
|
|
2699
|
-
roleClass: 'worker',
|
|
2700
|
-
isHumanSeat: false,
|
|
2701
|
-
name: 'Routed Reviewer',
|
|
2702
|
-
});
|
|
2703
|
-
await environment.admin.referenceRoleFromTaxonomy(roleContractCube, referencedRole);
|
|
2704
2884
|
const activeRole = await environment.admin.createRole(roleContractCube, {
|
|
2705
2885
|
roleClass: 'worker',
|
|
2706
2886
|
isHumanSeat: false,
|
|
@@ -2712,7 +2892,6 @@ export async function runAdapterConformance(
|
|
|
2712
2892
|
[defaultRole, ErrorCode.DEFAULT_ROLE_REQUIRED, 'default role'],
|
|
2713
2893
|
[mandatoryRole, ErrorCode.ROLE_REQUIRED, 'mandatory role'],
|
|
2714
2894
|
[humanRole, ErrorCode.ROLE_REQUIRED, 'human-seat role'],
|
|
2715
|
-
[referencedRole, ErrorCode.ROLE_REFERENCED, 'taxonomy-referenced role'],
|
|
2716
2895
|
[activeRole, ErrorCode.ROLE_IN_USE, 'active-drone role'],
|
|
2717
2896
|
] as const;
|
|
2718
2897
|
for (const [index, [role, code, label]] of refusals.entries()) {
|
|
@@ -2805,6 +2984,7 @@ export async function runAdapterConformance(
|
|
|
2805
2984
|
roleContractCube,
|
|
2806
2985
|
createProtocolEnvelope('role-delete-attribution', {
|
|
2807
2986
|
post_id: '00000000-0000-4000-8000-000000000311',
|
|
2987
|
+
to: 'broadcast',
|
|
2808
2988
|
message: 'attribution survives',
|
|
2809
2989
|
}),
|
|
2810
2990
|
);
|
|
@@ -2846,7 +3026,6 @@ export async function runAdapterConformance(
|
|
|
2846
3026
|
ErrorCode.ROLE_IN_USE,
|
|
2847
3027
|
ErrorCode.DEFAULT_ROLE_REQUIRED,
|
|
2848
3028
|
ErrorCode.ROLE_REQUIRED,
|
|
2849
|
-
ErrorCode.ROLE_REFERENCED,
|
|
2850
3029
|
],
|
|
2851
3030
|
in_use_message_actionable: true,
|
|
2852
3031
|
evicted_drone_retargeted: true,
|
|
@@ -3472,6 +3651,7 @@ export async function runAdapterConformance(
|
|
|
3472
3651
|
cube,
|
|
3473
3652
|
createProtocolEnvelope('delete-log', {
|
|
3474
3653
|
post_id: '00000000-0000-4000-8000-000000000312',
|
|
3654
|
+
to: 'broadcast',
|
|
3475
3655
|
message: 'deleted with cube',
|
|
3476
3656
|
}),
|
|
3477
3657
|
);
|