@stigmer/server 3.19.0 → 3.20.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.
Files changed (72) hide show
  1. package/dist/authorization/model/index.d.ts +1 -1
  2. package/dist/authorization/model/index.d.ts.map +1 -1
  3. package/dist/authorization/model/index.js +0 -2
  4. package/dist/authorization/model/index.js.map +1 -1
  5. package/dist/authorization/model/organization.d.ts.map +1 -1
  6. package/dist/authorization/model/organization.js +0 -1
  7. package/dist/authorization/model/organization.js.map +1 -1
  8. package/dist/boot/compose.d.ts.map +1 -1
  9. package/dist/boot/compose.js +2 -15
  10. package/dist/boot/compose.js.map +1 -1
  11. package/dist/boot/inprocess.d.ts +2 -13
  12. package/dist/boot/inprocess.d.ts.map +1 -1
  13. package/dist/boot/inprocess.js +6 -16
  14. package/dist/boot/inprocess.js.map +1 -1
  15. package/dist/domain/workflow/registry/data/task-kind-registry.json +0 -1
  16. package/dist/pipeline/interceptors/auth.d.ts +1 -1
  17. package/dist/pipeline/interceptors/auth.js +1 -1
  18. package/dist/query/search/registry.d.ts +3 -5
  19. package/dist/query/search/registry.d.ts.map +1 -1
  20. package/dist/query/search/registry.js +3 -7
  21. package/dist/query/search/registry.js.map +1 -1
  22. package/dist/store/postgres/migrations.d.ts +3 -1
  23. package/dist/store/postgres/migrations.d.ts.map +1 -1
  24. package/dist/store/postgres/migrations.js +19 -1
  25. package/dist/store/postgres/migrations.js.map +1 -1
  26. package/dist/store/sqlite/migrations.d.ts +3 -1
  27. package/dist/store/sqlite/migrations.d.ts.map +1 -1
  28. package/dist/store/sqlite/migrations.js +22 -1
  29. package/dist/store/sqlite/migrations.js.map +1 -1
  30. package/package.json +6 -6
  31. package/src/authorization/__tests__/derived-tuples.test.ts +1 -1
  32. package/src/authorization/__tests__/fixtures/fga/org-admin-owner-inheritance.fga.yaml +6 -6
  33. package/src/authorization/__tests__/wire-permissions.test.ts +0 -1
  34. package/src/authorization/model/__tests__/registry.test.ts +0 -9
  35. package/src/authorization/model/index.ts +1 -3
  36. package/src/authorization/model/organization.ts +0 -1
  37. package/src/boot/compose.ts +2 -15
  38. package/src/boot/inprocess.ts +8 -29
  39. package/src/domain/iampolicy/__tests__/role-lifecycle.test.ts +0 -1
  40. package/src/domain/workflow/registry/data/task-kind-registry.json +0 -1
  41. package/src/pipeline/__tests__/router-transport.test.ts +1 -1
  42. package/src/pipeline/interceptors/auth.ts +1 -1
  43. package/src/query/__tests__/query-services.test.ts +16 -18
  44. package/src/query/search/__tests__/criteria.test.ts +3 -6
  45. package/src/query/search/__tests__/query-store.test.ts +21 -21
  46. package/src/query/search/registry.ts +3 -7
  47. package/src/store/postgres/__tests__/migrations.test.ts +23 -4
  48. package/src/store/postgres/migrations.ts +20 -1
  49. package/src/store/sqlite/__tests__/migrations.test.ts +26 -2
  50. package/src/store/sqlite/migrations.ts +23 -1
  51. package/dist/authorization/model/project.d.ts +0 -2
  52. package/dist/authorization/model/project.d.ts.map +0 -1
  53. package/dist/authorization/model/project.js +0 -32
  54. package/dist/authorization/model/project.js.map +0 -1
  55. package/dist/domain/project/controller.d.ts +0 -48
  56. package/dist/domain/project/controller.d.ts.map +0 -1
  57. package/dist/domain/project/controller.js +0 -192
  58. package/dist/domain/project/controller.js.map +0 -1
  59. package/dist/domain/project/reconcile.d.ts +0 -77
  60. package/dist/domain/project/reconcile.d.ts.map +0 -1
  61. package/dist/domain/project/reconcile.js +0 -179
  62. package/dist/domain/project/reconcile.js.map +0 -1
  63. package/dist/domain/project/search-extractor.d.ts +0 -3
  64. package/dist/domain/project/search-extractor.d.ts.map +0 -1
  65. package/dist/domain/project/search-extractor.js +0 -43
  66. package/dist/domain/project/search-extractor.js.map +0 -1
  67. package/src/authorization/model/project.ts +0 -47
  68. package/src/domain/project/__tests__/project.composed.test.ts +0 -381
  69. package/src/domain/project/__tests__/reconcile.test.ts +0 -447
  70. package/src/domain/project/controller.ts +0 -387
  71. package/src/domain/project/reconcile.ts +0 -296
  72. package/src/domain/project/search-extractor.ts +0 -71
@@ -1,179 +0,0 @@
1
- /**
2
- * Project membership reconciliation — ports pkg/domain/project/reconcile/
3
- * (service.go, execution_engine.go, reconciliation_{result,options,error}.go).
4
- * Proven by __tests__/reconcile.test.ts and the apply→prune composed arms in
5
- * __tests__/project.composed.test.ts.
6
- *
7
- * Apply compares the previous project's spec.members with the newly persisted
8
- * spec.members, both keyed "{kind}:{slug}" (the kind's enum NAME — Go's %s).
9
- * Members only in the current list are ADDED; members only in the previous
10
- * list are ORPHANS and, with pruning enabled, are deleted through the four
11
- * downstream domains' in-process clients — their FULL delete pipelines, so a
12
- * referentially-blocked orphan fails exactly as an external delete would.
13
- *
14
- * Error posture (Go, verified): reconcile NEVER throws. Per-orphan failures
15
- * are collected on the result and are log-only — ToProtoSummary carries only
16
- * created/deleted, so a failed orphan is simply absent from `deleted`. The
17
- * wire surface of this whole module is those two lists.
18
- *
19
- * Deliberately not ported (plan-gate decisions, tasks/T01_0_plan.md):
20
- * - Go's nil-deleter stub arm (service.go:95-98) and the
21
- * SetReconciliationService late-bind — both exist only for Go's
22
- * registration-before-clients boot window; the compose root's lazy
23
- * provider closes that window, so here a deleter always exists.
24
- * - ResultBuilder and the DryRun/NoPrune option factories — zero non-test
25
- * uses in Go; the sole production call is
26
- * reconcile(previous, current, DEFAULT_RECONCILIATION_OPTIONS).
27
- */
28
- import { create } from "@bufbuild/protobuf";
29
- import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
30
- import { McpServerSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
31
- import { SkillSchema } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/api_pb";
32
- import { WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
33
- import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
34
- import { ReconciliationSummarySchema } from "@stigmer/protos/ai/stigmer/tenancy/project/v1/status_pb";
35
- import { findResourceBySlug } from "../../pipeline/steps/helpers.js";
36
- import { metadataOf } from "../../pipeline/steps/shapes.js";
37
- export const DEFAULT_RECONCILIATION_OPTIONS = {
38
- pruneEnabled: true,
39
- dryRun: false,
40
- };
41
- export function newReconciliationService(deps) {
42
- return {
43
- async reconcile(previousMembers, currentMembers, options) {
44
- const previousSet = buildReferenceSet(previousMembers);
45
- const currentSet = buildReferenceSet(currentMembers);
46
- const added = currentMembers.filter((ref) => !previousSet.has(referenceKey(ref)));
47
- const orphans = previousMembers.filter((ref) => !currentSet.has(referenceKey(ref)));
48
- if (added.length === 0 && orphans.length === 0) {
49
- return { added: [], removed: [], errors: [] };
50
- }
51
- // Dry run reports the orphans in `removed` WITHOUT deleting (Go
52
- // service.go:61-63 passes them straight through).
53
- if (options.dryRun) {
54
- return { added, removed: orphans, errors: [] };
55
- }
56
- if (!options.pruneEnabled || orphans.length === 0) {
57
- return { added, removed: [], errors: [] };
58
- }
59
- const { removed, errors } = await deleteOrphans(deps, orphans);
60
- return { added, removed, errors };
61
- },
62
- };
63
- }
64
- /**
65
- * Deletes each orphan, continuing on failure (Go deleteOrphans). Failures
66
- * are warned here — Go collects them onto a result nothing in production
67
- * reads, which leaves operators blind; the log is edition-local and the
68
- * wire behavior is identical.
69
- */
70
- async function deleteOrphans(deps, orphans) {
71
- const removed = [];
72
- const errors = [];
73
- for (const ref of orphans) {
74
- const refKey = referenceKey(ref);
75
- let resourceId;
76
- try {
77
- resourceId = await resolveResourceId(deps.store, ref);
78
- }
79
- catch (cause) {
80
- errors.push({
81
- resourceKey: refKey,
82
- message: "failed to resolve resource for deletion",
83
- cause,
84
- });
85
- deps.logger.warn("reconciliation: failed to resolve orphan for deletion", {
86
- resource: refKey,
87
- error: cause instanceof Error ? cause.message : String(cause),
88
- });
89
- continue;
90
- }
91
- try {
92
- await deps.orphanDeleter().delete(ref.kind, resourceId);
93
- }
94
- catch (cause) {
95
- errors.push({
96
- resourceKey: refKey,
97
- message: "failed to delete orphaned resource",
98
- cause,
99
- });
100
- deps.logger.warn("reconciliation: failed to delete orphaned resource", {
101
- resource: refKey,
102
- resourceId,
103
- error: cause instanceof Error ? cause.message : String(cause),
104
- });
105
- continue;
106
- }
107
- removed.push(ref);
108
- }
109
- return { removed, errors };
110
- }
111
- /**
112
- * Resolves an orphan reference to its resource id, within the org the
113
- * reference names. Slugs are org-scoped identifiers: a local backend holds
114
- * the `stigmer` system org beside the user's, a self-host holds many, and
115
- * the same slug may exist in several. The Go port matched by slug alone and
116
- * could prune another org's resource (stigmer/stigmer#1151); the CLI has
117
- * always stamped `org` on every member reference, so scoping is a change
118
- * of lookup, not of contract. A reference without an org (a project written
119
- * before members carried one) keeps the unscoped match, the helper's
120
- * documented empty-org arm.
121
- */
122
- async function resolveResourceId(store, ref) {
123
- const schema = schemaForKind(ref.kind);
124
- const resource = await findResourceBySlug(store, ref.kind, schema, ref.slug, ref.org);
125
- if (resource === undefined) {
126
- const where = ref.org === "" ? "" : ` in org '${ref.org}'`;
127
- throw new Error(`resource ${kindName(ref.kind)}/${ref.slug} not found${where}`);
128
- }
129
- const id = metadataOf(resource)?.id ?? "";
130
- if (id === "") {
131
- throw new Error(`resource ${kindName(ref.kind)}/${ref.slug} has no ID`);
132
- }
133
- return id;
134
- }
135
- /**
136
- * The four member kinds the reconciler supports (Go newProtoForKind);
137
- * anything else is an unsupported-kind resolution error.
138
- */
139
- function schemaForKind(kind) {
140
- switch (kind) {
141
- case ApiResourceKind.agent:
142
- return AgentSchema;
143
- case ApiResourceKind.workflow:
144
- return WorkflowSchema;
145
- case ApiResourceKind.mcp_server:
146
- return McpServerSchema;
147
- case ApiResourceKind.skill:
148
- return SkillSchema;
149
- default:
150
- throw new Error(`unsupported resource kind: ${kindName(kind)}`);
151
- }
152
- }
153
- /**
154
- * "{kind}:{slug}" with the kind's enum NAME — Go fmt %s of the enum (e.g.
155
- * "agent:my-agent", "mcp_server:tools"). The numeric form would diverge
156
- * from Go's log/error copy.
157
- */
158
- export function referenceKey(ref) {
159
- return `${kindName(ref.kind)}:${ref.slug}`;
160
- }
161
- function kindName(kind) {
162
- return ApiResourceKind[kind] ?? String(kind);
163
- }
164
- function buildReferenceSet(refs) {
165
- return new Set(refs.map(referenceKey));
166
- }
167
- /**
168
- * Go ToProtoSummary: added → created, updated ALWAYS empty (not applicable
169
- * in the reference model), removed → deleted. Every successful apply sets
170
- * the summary — even an all-empty one is a PRESENT message on the wire.
171
- */
172
- export function toProtoSummary(result) {
173
- return create(ReconciliationSummarySchema, {
174
- created: [...result.added],
175
- updated: [],
176
- deleted: [...result.removed],
177
- });
178
- }
179
- //# sourceMappingURL=reconcile.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../../../src/domain/project/reconcile.ts"],"names":[],"mappings":"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"}
@@ -1,3 +0,0 @@
1
- import type { SearchableExtractor } from "../../query/search/extractor.js";
2
- export declare const projectSearchExtractor: SearchableExtractor;
3
- //# sourceMappingURL=search-extractor.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"search-extractor.d.ts","sourceRoot":"","sources":["../../../src/domain/project/search-extractor.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAE3E,eAAO,MAAM,sBAAsB,EAAE,mBA0CpC,CAAC"}
@@ -1,43 +0,0 @@
1
- import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
2
- import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
3
- import { ProjectSchema } from "@stigmer/protos/ai/stigmer/tenancy/project/v1/api_pb";
4
- import { buildSearchResult } from "../../query/search/extractor.js";
5
- export const projectSearchExtractor = {
6
- kind: ApiResourceKind.project,
7
- schema: ProjectSchema,
8
- getSearchSummary(resource) {
9
- // Go ProjectExtractor.GetSearchSummary: spec.description.
10
- const project = resource;
11
- return project.spec?.description ?? "";
12
- },
13
- toSearchResult(resource, score) {
14
- const project = resource;
15
- return buildSearchResult({
16
- kind: ApiResourceKind.project,
17
- metadata: project.metadata,
18
- summary: project.spec?.description ?? "",
19
- score,
20
- createdAt: project.status?.audit?.specAudit?.createdAt,
21
- updatedAt: project.status?.audit?.specAudit?.updatedAt,
22
- });
23
- },
24
- getSearchIndexEntry(resource) {
25
- const project = resource;
26
- const metadata = project.metadata;
27
- if (metadata === undefined) {
28
- return undefined;
29
- }
30
- return {
31
- name: metadata.name,
32
- description: project.spec?.description ?? "",
33
- // Tags join space-separated — the index entry carries one tags
34
- // string (Go extractor.JoinTags).
35
- tags: metadata.tags.join(" "),
36
- org: metadata.org,
37
- // The enum NAME string, exactly Go's visibility.String().
38
- visibility: ApiResourceVisibility[metadata.visibility] ?? "",
39
- createdAt: Number(project.status?.audit?.specAudit?.createdAt?.seconds ?? 0n),
40
- };
41
- },
42
- };
43
- //# sourceMappingURL=search-extractor.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"search-extractor.js","sourceRoot":"","sources":["../../../src/domain/project/search-extractor.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,eAAe,EAAE,MAAM,qFAAqF,CAAC;AACtH,OAAO,EAAE,qBAAqB,EAAE,MAAM,wDAAwD,CAAC;AAE/F,OAAO,EAAE,aAAa,EAAE,MAAM,sDAAsD,CAAC;AAIrF,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAGpE,MAAM,CAAC,MAAM,sBAAsB,GAAwB;IACzD,IAAI,EAAE,eAAe,CAAC,OAAO;IAC7B,MAAM,EAAE,aAAa;IAErB,gBAAgB,CAAC,QAAiB;QAChC,0DAA0D;QAC1D,MAAM,OAAO,GAAG,QAA8B,CAAC;QAC/C,OAAO,OAAO,CAAC,IAAI,EAAE,WAAW,IAAI,EAAE,CAAC;IACzC,CAAC;IAED,cAAc,CAAC,QAAiB,EAAE,KAAa;QAC7C,MAAM,OAAO,GAAG,QAA8B,CAAC;QAC/C,OAAO,iBAAiB,CAAC;YACvB,IAAI,EAAE,eAAe,CAAC,OAAO;YAC7B,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,OAAO,EAAE,OAAO,CAAC,IAAI,EAAE,WAAW,IAAI,EAAE;YACxC,KAAK;YACL,SAAS,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;YACtD,SAAS,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;SACvD,CAAC,CAAC;IACL,CAAC;IAED,mBAAmB,CAAC,QAAiB;QACnC,MAAM,OAAO,GAAG,QAA8B,CAAC;QAC/C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QAClC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO;YACL,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,WAAW,EAAE,OAAO,CAAC,IAAI,EAAE,WAAW,IAAI,EAAE;YAC5C,+DAA+D;YAC/D,kCAAkC;YAClC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;YAC7B,GAAG,EAAE,QAAQ,CAAC,GAAG;YACjB,0DAA0D;YAC1D,UAAU,EAAE,qBAAqB,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,EAAE;YAC5D,SAAS,EAAE,MAAM,CACf,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,IAAI,EAAE,CAC3D;SACF,CAAC;IACJ,CAAC;CACF,CAAC"}
@@ -1,47 +0,0 @@
1
- /**
2
- * Transcript of fga/model/tenancy/project.fga — an organization's
3
- * grouping: admins own it, and `viewer from organization` lets every
4
- * organization viewer read it without a visibility tuple (projects have
5
- * no visibility axis in `kind_meta`; the whole organization is their
6
- * audience by the model's own line).
7
- */
8
- import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
9
- import { ProjectSchema } from "@stigmer/protos/ai/stigmer/tenancy/project/v1/api_pb";
10
-
11
- import {
12
- computed,
13
- declareKind,
14
- direct,
15
- from,
16
- objectOf,
17
- union,
18
- } from "./rewrite.js";
19
-
20
- export const projectDeclaration = declareKind({
21
- kind: ApiResourceKind.project,
22
- schema: ProjectSchema,
23
- source: "fga/model/tenancy/project.fga",
24
- relations: [
25
- ["organization", direct(objectOf("organization"))],
26
- [
27
- "owner",
28
- union(
29
- direct(objectOf("identity_account")),
30
- from("admin", "organization"),
31
- ),
32
- ],
33
- [
34
- "viewer",
35
- union(
36
- direct(objectOf("identity_account")),
37
- computed("owner"),
38
- from("viewer", "organization"),
39
- ),
40
- ],
41
- ["can_view", computed("viewer")],
42
- ["can_edit", computed("owner")],
43
- ["can_delete", computed("owner")],
44
- ["can_grant_access", computed("owner")],
45
- ["can_view_access", computed("viewer")],
46
- ],
47
- });
@@ -1,381 +0,0 @@
1
- /**
2
- * Pins the project domain against Go's controller tests — through the REAL
3
- * stack: a composed server on an ephemeral port, a native gRPC client, the
4
- * full interceptor chain, and REAL orphan deletes through the in-process
5
- * command clients' full pipelines.
6
- *
7
- * The load-bearing pins the conformance suite does NOT cover (it never
8
- * touches spec.members or last_reconciliation):
9
- * - every successful apply response carries status.last_reconciliation,
10
- * even as a present-but-EMPTY message — and it is NEVER persisted;
11
- * - apply→prune end to end: a dropped member is actually deleted through
12
- * its owning domain's pipeline; an unresolvable member is silently
13
- * absent from `deleted` while apply still succeeds;
14
- * - members referencing nonexistent resources persist silently (no
15
- * ValidateReferences in any project chain);
16
- * - the create/update pipeline ASYMMETRY: ValidateVisibility and
17
- * NormalizeReferences run on create only;
18
- * - project delete never cascades to members.
19
- */
20
- import { mkdtempSync, rmSync } from "node:fs";
21
- import { tmpdir } from "node:os";
22
- import path from "node:path";
23
-
24
- import { create } from "@bufbuild/protobuf";
25
- import { Code, ConnectError, createClient } from "@connectrpc/connect";
26
- import type { Client, Transport } from "@connectrpc/connect";
27
- import { createGrpcTransport } from "@connectrpc/connect-node";
28
- import { afterAll, beforeAll, describe, expect, it } from "vitest";
29
-
30
- import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
31
- import { AgentQueryController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/query_pb";
32
- import { McpServerSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
33
- import { McpServerQueryController } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/query_pb";
34
- import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
35
- import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
36
- import { ProjectCommandController } from "@stigmer/protos/ai/stigmer/tenancy/project/v1/command_pb";
37
- import { ProjectQueryController } from "@stigmer/protos/ai/stigmer/tenancy/project/v1/query_pb";
38
-
39
- import { loadConfig } from "../../../boot/config.js";
40
- import { composeServer } from "../../../boot/compose.js";
41
- import type { ComposedServer } from "../../../boot/compose.js";
42
- import { createLogger } from "../../../boot/logger.js";
43
-
44
- const silentLogger = createLogger({ level: "error", pretty: false, write: () => {} });
45
-
46
- let dir: string;
47
- let server: ComposedServer;
48
- let transport: Transport;
49
- let command: Client<typeof ProjectCommandController>;
50
- let query: Client<typeof ProjectQueryController>;
51
- let agentQuery: Client<typeof AgentQueryController>;
52
- let mcpServerQuery: Client<typeof McpServerQueryController>;
53
-
54
- beforeAll(async () => {
55
- dir = mkdtempSync(path.join(tmpdir(), "project-domain-test-"));
56
- server = await composeServer({
57
- config: loadConfig({
58
- STIGMER_MODEL_REGISTRY_REFRESH: "off",
59
- // No engine behind composed tests: 127.0.0.1:1 is deterministically
60
- // closed, so boots fail the non-fatal connect fast and can never touch
61
- // a live local Temporal (the conformance CRUD harness does the same).
62
- TEMPORAL_HOST_PORT: "127.0.0.1:1",
63
- DB_PATH: path.join(dir, "stigmer.db"),
64
- // Everything filesystem-backed stays inside the test dir — the
65
- // defaults resolve into ~/.stigmer, which tests must never touch.
66
- STORAGE_PATH: path.join(dir, "storage"),
67
- ARTIFACT_LOCAL_BASE_PATH: path.join(dir, "artifacts"),
68
- }),
69
- logger: silentLogger,
70
- portOverride: 0,
71
- host: "127.0.0.1",
72
- });
73
- const port = await server.start();
74
- transport = createGrpcTransport({ baseUrl: `http://127.0.0.1:${port}` });
75
- command = createClient(ProjectCommandController, transport);
76
- query = createClient(ProjectQueryController, transport);
77
- agentQuery = createClient(AgentQueryController, transport);
78
- mcpServerQuery = createClient(McpServerQueryController, transport);
79
- });
80
-
81
- afterAll(async () => {
82
- await server.shutdown();
83
- rmSync(dir, { recursive: true, force: true });
84
- });
85
-
86
- const API_VERSION = "tenancy.stigmer.ai/v1";
87
- const KIND = "Project";
88
- const ORG = "local";
89
-
90
- let seq = 0;
91
- function uniqueName(prefix: string): string {
92
- seq += 1;
93
- return `${prefix} ${seq}`;
94
- }
95
-
96
- interface MemberInit {
97
- kind: ApiResourceKind;
98
- slug: string;
99
- org?: string;
100
- }
101
-
102
- function projectInput(
103
- name: string,
104
- extras?: { members?: MemberInit[]; visibility?: ApiResourceVisibility },
105
- ) {
106
- return {
107
- apiVersion: API_VERSION,
108
- kind: KIND,
109
- metadata: {
110
- name,
111
- org: ORG,
112
- ...(extras?.visibility !== undefined ? { visibility: extras.visibility } : {}),
113
- },
114
- spec: {
115
- description: "created by the domain test",
116
- members: (extras?.members ?? []).map((m) => ({
117
- kind: m.kind,
118
- slug: m.slug,
119
- org: m.org ?? ORG,
120
- })),
121
- },
122
- };
123
- }
124
-
125
- async function seedAgent(id: string, slug: string): Promise<void> {
126
- await server.store.saveResource(
127
- ApiResourceKind.agent,
128
- id,
129
- AgentSchema,
130
- create(AgentSchema, {
131
- apiVersion: "agentic.stigmer.ai/v1",
132
- kind: "Agent",
133
- metadata: { id, name: slug, slug, org: ORG },
134
- }),
135
- );
136
- }
137
-
138
- async function seedMcpServer(id: string, slug: string): Promise<void> {
139
- await server.store.saveResource(
140
- ApiResourceKind.mcp_server,
141
- id,
142
- McpServerSchema,
143
- create(McpServerSchema, {
144
- apiVersion: "agentic.stigmer.ai/v1",
145
- kind: "McpServer",
146
- metadata: { id, name: slug, slug, org: ORG },
147
- }),
148
- );
149
- }
150
-
151
- async function grpcCode(run: () => Promise<unknown>): Promise<ConnectError> {
152
- try {
153
- await run();
154
- throw new Error("expected the call to fail");
155
- } catch (error) {
156
- if (error instanceof ConnectError) {
157
- return error;
158
- }
159
- throw error;
160
- }
161
- }
162
-
163
- describe("apply reconciliation summary (Go apply_test.go)", () => {
164
- it("first apply reports all members as created and the prj_ id shape", async () => {
165
- const applied = await command.apply(
166
- projectInput(uniqueName("Members Project"), {
167
- members: [
168
- { kind: ApiResourceKind.agent, slug: "member-a" },
169
- { kind: ApiResourceKind.workflow, slug: "member-b" },
170
- ],
171
- }),
172
- );
173
-
174
- expect(applied.metadata?.id).toMatch(/^prj_[0-9a-z]{26}$/);
175
- expect(applied.status?.audit?.specAudit?.event).toBe("created");
176
- expect(applied.status?.lastReconciliation).toBeDefined();
177
- expect(
178
- applied.status?.lastReconciliation?.created.map((r) => r.slug),
179
- ).toEqual(["member-a", "member-b"]);
180
- expect(applied.status?.lastReconciliation?.updated).toEqual([]);
181
- expect(applied.status?.lastReconciliation?.deleted).toEqual([]);
182
- });
183
-
184
- it("a no-change apply still carries a PRESENT (empty) summary — and it is never persisted", async () => {
185
- const name = uniqueName("Summary Presence");
186
- const first = await command.apply(projectInput(name));
187
- // No members at all: the summary message must still be present.
188
- expect(first.status?.lastReconciliation).toBeDefined();
189
- expect(first.status?.lastReconciliation?.created).toEqual([]);
190
- expect(first.status?.lastReconciliation?.deleted).toEqual([]);
191
-
192
- // Response-only: the persisted resource has NO last_reconciliation.
193
- const fetched = await query.get({ value: first.metadata!.id });
194
- expect(fetched.status?.lastReconciliation).toBeUndefined();
195
-
196
- // And the second apply's summary is computed fresh from the delta.
197
- const second = await command.apply(projectInput(name));
198
- expect(second.metadata?.id).toBe(first.metadata?.id);
199
- expect(second.status?.audit?.specAudit?.event).toBe("updated");
200
- expect(second.status?.lastReconciliation).toBeDefined();
201
- expect(second.status?.lastReconciliation?.created).toEqual([]);
202
- });
203
-
204
- it("prunes a dropped member FOR REAL through its domain's delete pipeline", async () => {
205
- await seedAgent("agt_prune_keep", "prune-keep-agent");
206
- await seedMcpServer("mcps_prune_drop", "prune-drop-mcps");
207
- const name = uniqueName("Orphan Prune");
208
-
209
- await command.apply(
210
- projectInput(name, {
211
- members: [
212
- { kind: ApiResourceKind.agent, slug: "prune-keep-agent" },
213
- { kind: ApiResourceKind.mcp_server, slug: "prune-drop-mcps" },
214
- ],
215
- }),
216
- );
217
-
218
- // The second apply both DROPS the mcpserver and INTRODUCES a new
219
- // member, so one wire summary carries created and deleted together.
220
- const second = await command.apply(
221
- projectInput(name, {
222
- members: [
223
- { kind: ApiResourceKind.agent, slug: "prune-keep-agent" },
224
- { kind: ApiResourceKind.workflow, slug: "prune-new-member" },
225
- ],
226
- }),
227
- );
228
-
229
- expect(
230
- second.status?.lastReconciliation?.deleted.map((r) => r.slug),
231
- ).toEqual(["prune-drop-mcps"]);
232
- expect(
233
- second.status?.lastReconciliation?.created.map((r) => r.slug),
234
- ).toEqual(["prune-new-member"]);
235
-
236
- // The orphan is GONE (deleted through mcpserver's own pipeline)…
237
- const gone = await grpcCode(() =>
238
- mcpServerQuery.get({ value: "mcps_prune_drop" }),
239
- );
240
- expect(gone.code).toBe(Code.NotFound);
241
- // …and the retained member is untouched.
242
- const kept = await agentQuery.get({ value: "agt_prune_keep" });
243
- expect(kept.metadata?.slug).toBe("prune-keep-agent");
244
- });
245
-
246
- it("an id-carrying apply captures previous members and prunes on the update path", async () => {
247
- await seedAgent("agt_idpath", "idpath-agent");
248
- const name = uniqueName("Id Carrying Apply");
249
-
250
- const first = await command.apply(
251
- projectInput(name, {
252
- members: [{ kind: ApiResourceKind.agent, slug: "idpath-agent" }],
253
- }),
254
- );
255
-
256
- // Second apply addresses the project by its ID (the CLI's steady-state
257
- // shape) with the member dropped — previousMembers must still be
258
- // captured from the loaded existing resource.
259
- const second = await command.apply({
260
- ...projectInput(name),
261
- metadata: { id: first.metadata!.id, name, org: ORG },
262
- });
263
-
264
- expect(second.metadata?.id).toBe(first.metadata?.id);
265
- expect(
266
- second.status?.lastReconciliation?.deleted.map((r) => r.slug),
267
- ).toEqual(["idpath-agent"]);
268
- const gone = await grpcCode(() => agentQuery.get({ value: "agt_idpath" }));
269
- expect(gone.code).toBe(Code.NotFound);
270
- });
271
-
272
- it("a member dropped via plain UPDATE is stranded — the next apply prunes nothing", async () => {
273
- // Update runs NO reconciliation, and apply diffs against the STORED
274
- // members: dropping a member through update shrinks the stored list
275
- // first, so the later apply computes no orphan and the resource
276
- // survives. User-visible Go behavior (update strands, apply prunes).
277
- await seedAgent("agt_stranded", "stranded-agent");
278
- const name = uniqueName("Update Strands");
279
-
280
- await command.apply(
281
- projectInput(name, {
282
- members: [{ kind: ApiResourceKind.agent, slug: "stranded-agent" }],
283
- }),
284
- );
285
- await command.update(projectInput(name));
286
-
287
- const applied = await command.apply(projectInput(name));
288
- expect(applied.status?.lastReconciliation?.deleted).toEqual([]);
289
- const survivor = await agentQuery.get({ value: "agt_stranded" });
290
- expect(survivor.metadata?.slug).toBe("stranded-agent");
291
- });
292
-
293
- it("an unresolvable orphan is silently absent from deleted; apply still succeeds", async () => {
294
- const name = uniqueName("Ghost Member");
295
- // No ValidateReferences in any project chain: a member that points at
296
- // nothing persists silently (pinned Go behavior).
297
- const first = await command.apply(
298
- projectInput(name, {
299
- members: [{ kind: ApiResourceKind.agent, slug: "ghost-agent" }],
300
- }),
301
- );
302
- expect(first.spec?.members.map((m) => m.slug)).toEqual(["ghost-agent"]);
303
-
304
- const second = await command.apply(projectInput(name));
305
- // Resolution failed (nothing to delete) → collected log-only, absent
306
- // from the wire's deleted list, and apply SUCCEEDS.
307
- expect(second.status?.lastReconciliation).toBeDefined();
308
- expect(second.status?.lastReconciliation?.deleted).toEqual([]);
309
- });
310
- });
311
-
312
- describe("create/update pipeline asymmetry (create.go vs update.go)", () => {
313
- it("create rejects an unsupported visibility level; update IGNORES the carried level", async () => {
314
- // Project's kind_meta has no VisibilityConfig → private-only.
315
- const rejected = await grpcCode(() =>
316
- command.create(
317
- projectInput(uniqueName("Visibility Reject"), {
318
- visibility: ApiResourceVisibility.visibility_org,
319
- }),
320
- ),
321
- );
322
- expect(rejected.code).toBe(Code.InvalidArgument);
323
-
324
- // Update has NO ValidateVisibility step; the carried level is ignored
325
- // (visibility is update-immutable, oss#573) — never rejected.
326
- const name = uniqueName("Visibility Ignore");
327
- const created = await command.create(projectInput(name));
328
- const updated = await command.update(
329
- projectInput(name, { visibility: ApiResourceVisibility.visibility_org }),
330
- );
331
- expect(updated.metadata?.id).toBe(created.metadata?.id);
332
- expect(updated.metadata?.visibility).toBe(
333
- ApiResourceVisibility.visibility_private,
334
- );
335
- });
336
-
337
- it("create normalizes empty member orgs; update persists them EMPTY", async () => {
338
- const name = uniqueName("Normalize Asymmetry");
339
- const created = await command.create(
340
- projectInput(name, {
341
- members: [{ kind: ApiResourceKind.agent, slug: "norm-a", org: "" }],
342
- }),
343
- );
344
- // NormalizeReferences filled the empty org from metadata.org.
345
- expect(created.spec?.members[0]?.org).toBe(ORG);
346
-
347
- const updated = await command.update(
348
- projectInput(name, {
349
- members: [{ kind: ApiResourceKind.agent, slug: "norm-b", org: "" }],
350
- }),
351
- );
352
- // Update runs NO NormalizeReferences: the empty org persists as-is.
353
- expect(updated.spec?.members[0]?.slug).toBe("norm-b");
354
- expect(updated.spec?.members[0]?.org).toBe("");
355
- });
356
- });
357
-
358
- describe("delete has no member cascade (delete.go)", () => {
359
- it("returns the pre-delete resource and leaves members untouched", async () => {
360
- await seedAgent("agt_cascade_safe", "cascade-safe-agent");
361
- const created = await command.create(
362
- projectInput(uniqueName("No Cascade"), {
363
- members: [{ kind: ApiResourceKind.agent, slug: "cascade-safe-agent" }],
364
- }),
365
- );
366
-
367
- const deleted = await command.delete({ value: created.metadata!.id });
368
- expect(deleted.metadata?.id).toBe(created.metadata?.id);
369
- expect(deleted.spec?.members.map((m) => m.slug)).toEqual([
370
- "cascade-safe-agent",
371
- ]);
372
-
373
- const notFound = await grpcCode(() =>
374
- query.get({ value: created.metadata!.id }),
375
- );
376
- expect(notFound.code).toBe(Code.NotFound);
377
- // The member survives: delete removes ONLY the project entity.
378
- const survivor = await agentQuery.get({ value: "agt_cascade_safe" });
379
- expect(survivor.metadata?.slug).toBe("cascade-safe-agent");
380
- });
381
- });