@stigmer/server 3.32.0 → 3.34.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 (76) hide show
  1. package/dist/authorization/model/data/authorization-model.json +6 -0
  2. package/dist/boot/compose.d.ts.map +1 -1
  3. package/dist/boot/compose.js +8 -1
  4. package/dist/boot/compose.js.map +1 -1
  5. package/dist/domain/apikey/controller.d.ts.map +1 -1
  6. package/dist/domain/apikey/controller.js +7 -2
  7. package/dist/domain/apikey/controller.js.map +1 -1
  8. package/dist/domain/apikey/verifier.d.ts +5 -0
  9. package/dist/domain/apikey/verifier.d.ts.map +1 -1
  10. package/dist/domain/apikey/verifier.js +32 -3
  11. package/dist/domain/apikey/verifier.js.map +1 -1
  12. package/dist/domain/organization/controller.js +14 -4
  13. package/dist/domain/organization/controller.js.map +1 -1
  14. package/dist/domain/platformclient/mint.d.ts.map +1 -1
  15. package/dist/domain/platformclient/mint.js +28 -1
  16. package/dist/domain/platformclient/mint.js.map +1 -1
  17. package/dist/domain/platformclient/resource-store.d.ts.map +1 -1
  18. package/dist/domain/platformclient/resource-store.js +13 -0
  19. package/dist/domain/platformclient/resource-store.js.map +1 -1
  20. package/dist/domain/platformclient/store-contract.d.ts.map +1 -1
  21. package/dist/domain/platformclient/store-contract.js +59 -0
  22. package/dist/domain/platformclient/store-contract.js.map +1 -1
  23. package/dist/domain/platformclient/store.d.ts +20 -0
  24. package/dist/domain/platformclient/store.d.ts.map +1 -1
  25. package/dist/domain/platformclient/store.js.map +1 -1
  26. package/dist/extensions/gate-slots.d.ts +11 -1
  27. package/dist/extensions/gate-slots.d.ts.map +1 -1
  28. package/dist/extensions/gate-slots.js +1 -0
  29. package/dist/extensions/gate-slots.js.map +1 -1
  30. package/dist/identity/credential-use.d.ts +74 -0
  31. package/dist/identity/credential-use.d.ts.map +1 -0
  32. package/dist/identity/credential-use.js +59 -0
  33. package/dist/identity/credential-use.js.map +1 -0
  34. package/dist/pipeline/steps/defaults.d.ts +10 -0
  35. package/dist/pipeline/steps/defaults.d.ts.map +1 -1
  36. package/dist/pipeline/steps/defaults.js +25 -0
  37. package/dist/pipeline/steps/defaults.js.map +1 -1
  38. package/dist/temporal/schedule/activities.d.ts.map +1 -1
  39. package/dist/temporal/schedule/activities.js +2 -1
  40. package/dist/temporal/schedule/activities.js.map +1 -1
  41. package/dist/temporal/schedule/run-starter.d.ts.map +1 -1
  42. package/dist/temporal/schedule/run-starter.js +2 -1
  43. package/dist/temporal/schedule/run-starter.js.map +1 -1
  44. package/dist/temporal/schedule/status-writes.d.ts +0 -6
  45. package/dist/temporal/schedule/status-writes.d.ts.map +1 -1
  46. package/dist/temporal/schedule/status-writes.js +8 -27
  47. package/dist/temporal/schedule/status-writes.js.map +1 -1
  48. package/dist/temporal/schedule/syncer.d.ts.map +1 -1
  49. package/dist/temporal/schedule/syncer.js +2 -1
  50. package/dist/temporal/schedule/syncer.js.map +1 -1
  51. package/package.json +6 -6
  52. package/src/authorization/__tests__/store-tests.test.ts +1 -0
  53. package/src/authorization/__tests__/wire-permissions.test.ts +0 -6
  54. package/src/authorization/model/data/authorization-model.json +6 -0
  55. package/src/boot/compose.ts +8 -1
  56. package/src/domain/apikey/__tests__/apikey.test.ts +17 -2
  57. package/src/domain/apikey/__tests__/verifier.test.ts +86 -3
  58. package/src/domain/apikey/controller.ts +12 -2
  59. package/src/domain/apikey/verifier.ts +46 -3
  60. package/src/domain/organization/controller.ts +17 -4
  61. package/src/domain/platformclient/__tests__/mint.test.ts +97 -11
  62. package/src/domain/platformclient/__tests__/resource-store.test.ts +4 -0
  63. package/src/domain/platformclient/mint.ts +34 -1
  64. package/src/domain/platformclient/resource-store.ts +18 -0
  65. package/src/domain/platformclient/store-contract.ts +100 -0
  66. package/src/domain/platformclient/store.ts +23 -0
  67. package/src/extensions/__tests__/extension-composition.test.ts +49 -3
  68. package/src/extensions/__tests__/registry.test.ts +3 -0
  69. package/src/extensions/gate-slots.ts +11 -0
  70. package/src/identity/__tests__/credential-use.test.ts +186 -0
  71. package/src/identity/credential-use.ts +130 -0
  72. package/src/pipeline/steps/defaults.ts +27 -0
  73. package/src/temporal/schedule/activities.ts +2 -1
  74. package/src/temporal/schedule/run-starter.ts +2 -1
  75. package/src/temporal/schedule/status-writes.ts +8 -31
  76. package/src/temporal/schedule/syncer.ts +2 -1
@@ -43,6 +43,16 @@
43
43
  * resource, and a team member who is not one of the organization's
44
44
  * viewers. Never on `bootstrapPolicy`, the platform's structural lane.
45
45
  *
46
+ * The ninth, `org-create:pre-side-effect-gate`: the organization create
47
+ * chain after its last pure step (CopySlugToId) and before Persist, the
48
+ * position the session chain's slot holds. It exists for a refusal that
49
+ * must leave nothing behind: `org-create:post-persist` runs after the row
50
+ * is written, so a limit enforced there would leave the organization it
51
+ * refused. The Cloud refuses a platform-managed organization its
52
+ * integrator's plan does not admit; a licensed deployment's organization
53
+ * limit belongs here too. `apply` delegates to create on its create arm,
54
+ * so the slot fires there as well.
55
+ *
46
56
  * Two enforcement layers, deliberately redundant, both derived from the
47
57
  * ONE literal tuple below (lockstep by construction):
48
58
  * - GateSlotName (compile time): the union of declared slot literals.
@@ -81,6 +91,7 @@ export const GATE_SLOT_NAMES = [
81
91
  "sandbox-acquisition:gate",
82
92
  "identity-account-provision:post-persist",
83
93
  "iam-policy-create:pre-side-effect-gate",
94
+ "org-create:pre-side-effect-gate",
84
95
  ] as const;
85
96
 
86
97
  /** The declared slot-name union — a registration outside it fails tsc. */
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Pins the credential last-use rule (credential-use.ts):
3
+ * - staleness is measured against the stored stamp, a never-used
4
+ * credential is stale, and exactly one resolution is stale again;
5
+ * - the stamp only moves forward, and a write bumps the status audit's
6
+ * updated_at + event while keeping who created the slot;
7
+ * - recordCredentialUse writes only when stale, treats a row deleted
8
+ * meanwhile as nothing to record, and logs any other failure without
9
+ * throwing.
10
+ */
11
+ import { create } from "@bufbuild/protobuf";
12
+ import { timestampDate, timestampFromDate } from "@bufbuild/protobuf/wkt";
13
+ import { describe, expect, it } from "vitest";
14
+
15
+ import {
16
+ ApiResourceAuditActorSchema,
17
+ ApiResourceAuditInfoSchema,
18
+ ApiResourceAuditSchema,
19
+ } from "@stigmer/protos/ai/stigmer/commons/apiresource/status_pb";
20
+
21
+ import { createLogger } from "../../boot/logger.js";
22
+ import { ResourceNotFoundError } from "../../store/interface.js";
23
+ import {
24
+ LAST_USED_RESOLUTION_MS,
25
+ lastUseIsStale,
26
+ recordCredentialUse,
27
+ stampLastUsed,
28
+ type CredentialUseStatus,
29
+ } from "../credential-use.js";
30
+
31
+ const NOW = new Date("2026-09-28T12:00:00Z");
32
+
33
+ function ago(ms: number): Date {
34
+ return new Date(NOW.getTime() - ms);
35
+ }
36
+
37
+ function capturingLogger(): {
38
+ lines: string[];
39
+ logger: ReturnType<typeof createLogger>;
40
+ } {
41
+ const lines: string[] = [];
42
+ return {
43
+ lines,
44
+ logger: createLogger({
45
+ level: "debug",
46
+ pretty: false,
47
+ write: (line) => lines.push(line),
48
+ }),
49
+ };
50
+ }
51
+
52
+ describe("lastUseIsStale", () => {
53
+ it("is stale for a credential never used", () => {
54
+ expect(lastUseIsStale(undefined, NOW)).toBe(true);
55
+ });
56
+
57
+ it("is fresh inside one resolution and stale from exactly one resolution on", () => {
58
+ const justInside = timestampFromDate(ago(LAST_USED_RESOLUTION_MS - 1));
59
+ const exactly = timestampFromDate(ago(LAST_USED_RESOLUTION_MS));
60
+ expect(lastUseIsStale(justInside, NOW)).toBe(false);
61
+ expect(lastUseIsStale(exactly, NOW)).toBe(true);
62
+ });
63
+
64
+ it("is fresh for a stamp ahead of this replica's clock", () => {
65
+ expect(lastUseIsStale(timestampFromDate(new Date(NOW.getTime() + 5_000)), NOW)).toBe(false);
66
+ });
67
+ });
68
+
69
+ describe("stampLastUsed", () => {
70
+ it("stamps a never-used status and bumps the status audit", () => {
71
+ const status: CredentialUseStatus = {};
72
+ expect(stampLastUsed(status, NOW)).toBe(true);
73
+ expect(timestampDate(status.lastUsedAt!)).toEqual(NOW);
74
+ expect(status.audit?.statusAudit?.event).toBe("updated");
75
+ expect(status.audit?.statusAudit?.updatedAt).toBeDefined();
76
+ });
77
+
78
+ it("keeps who created the status-audit slot and never touches the spec audit", () => {
79
+ const createdBy = create(ApiResourceAuditActorSchema, { id: "ida_creator" });
80
+ const createdAt = timestampFromDate(ago(86_400_000));
81
+ const specAudit = create(ApiResourceAuditInfoSchema, { createdBy, createdAt, event: "created" });
82
+ const priorSlot = create(ApiResourceAuditInfoSchema, { createdBy, createdAt, event: "created" });
83
+ const status: CredentialUseStatus = {
84
+ audit: create(ApiResourceAuditSchema, { specAudit, statusAudit: priorSlot }),
85
+ };
86
+
87
+ stampLastUsed(status, NOW);
88
+
89
+ expect(status.audit?.statusAudit?.createdBy?.id).toBe("ida_creator");
90
+ expect(status.audit?.statusAudit?.createdAt).toEqual(createdAt);
91
+ expect(status.audit?.statusAudit?.event).toBe("updated");
92
+ expect(status.audit?.specAudit).toBe(specAudit);
93
+ expect(priorSlot.event, "a new slot is set, the old one is not mutated").toBe("created");
94
+ });
95
+
96
+ it("only moves forward: a stamp at or after the use is kept, audit untouched", () => {
97
+ const later = timestampFromDate(new Date(NOW.getTime() + 1_000));
98
+ const status: CredentialUseStatus = { lastUsedAt: later };
99
+ expect(stampLastUsed(status, NOW)).toBe(false);
100
+ expect(status.lastUsedAt).toBe(later);
101
+ expect(status.audit).toBeUndefined();
102
+
103
+ const same = timestampFromDate(NOW);
104
+ const atSameInstant: CredentialUseStatus = { lastUsedAt: same };
105
+ expect(stampLastUsed(atSameInstant, NOW)).toBe(false);
106
+ });
107
+ });
108
+
109
+ describe("recordCredentialUse", () => {
110
+ function use(overrides: {
111
+ lastUsedAt?: Date;
112
+ write: (stamp: (status: CredentialUseStatus) => void) => Promise<unknown>;
113
+ logger?: ReturnType<typeof createLogger>;
114
+ }) {
115
+ return {
116
+ credential: "api key",
117
+ id: "key_1",
118
+ lastUsedAt:
119
+ overrides.lastUsedAt === undefined ? undefined : timestampFromDate(overrides.lastUsedAt),
120
+ now: NOW,
121
+ logger: overrides.logger ?? capturingLogger().logger,
122
+ write: overrides.write,
123
+ };
124
+ }
125
+
126
+ it("writes the stamp through the kind's atomic write when stale", async () => {
127
+ const row: CredentialUseStatus = {};
128
+ let writes = 0;
129
+ await recordCredentialUse(
130
+ use({
131
+ write: async (stamp) => {
132
+ writes += 1;
133
+ stamp(row);
134
+ },
135
+ }),
136
+ );
137
+ expect(writes).toBe(1);
138
+ expect(timestampDate(row.lastUsedAt!)).toEqual(NOW);
139
+ });
140
+
141
+ it("does not write inside the resolution", async () => {
142
+ let writes = 0;
143
+ await recordCredentialUse(
144
+ use({
145
+ lastUsedAt: ago(LAST_USED_RESOLUTION_MS / 2),
146
+ write: async () => {
147
+ writes += 1;
148
+ },
149
+ }),
150
+ );
151
+ expect(writes).toBe(0);
152
+ });
153
+
154
+ it("treats a row deleted meanwhile as nothing to record, silently", async () => {
155
+ const { lines, logger } = capturingLogger();
156
+ await recordCredentialUse(
157
+ use({
158
+ logger,
159
+ write: async () => {
160
+ throw new ResourceNotFoundError("api_key/key_1");
161
+ },
162
+ }),
163
+ );
164
+ expect(lines).toEqual([]);
165
+ });
166
+
167
+ it("logs any other failure with the credential id and never throws", async () => {
168
+ const { lines, logger } = capturingLogger();
169
+ await expect(
170
+ recordCredentialUse(
171
+ use({
172
+ logger,
173
+ write: async () => {
174
+ throw new Error("database unavailable");
175
+ },
176
+ }),
177
+ ),
178
+ ).resolves.toBeUndefined();
179
+ expect(lines).toHaveLength(1);
180
+ const entry = JSON.parse(lines[0]!) as Record<string, unknown>;
181
+ expect(entry.level).toBe("warn");
182
+ expect(entry.credential).toBe("api key");
183
+ expect(entry.id).toBe("key_1");
184
+ expect(entry.error).toBe("database unavailable");
185
+ });
186
+ });
@@ -0,0 +1,130 @@
1
+ /**
2
+ * When a credential was last used: the one rule the two credential kinds
3
+ * that carry `status.last_used_at` share. An API key is stamped by the
4
+ * identity verifier that accepts it (domain/apikey/verifier.ts), a
5
+ * PlatformClient by a successful mint (domain/platformclient/mint.ts). It
6
+ * lives here, beside the other infrastructure the credential lanes share,
7
+ * because neither domain owns the other (stigmer/stigmer#1255).
8
+ *
9
+ * The stamp is status the platform writes on its own, so it follows the
10
+ * house rule for such writes: an atomic read-modify-write on the row
11
+ * (`Store.updateResource`; the PlatformClient port's `modifyById`), the
12
+ * narrow status-audit bump (pipeline/steps/defaults.ts), and best-effort
13
+ * with a loud log, as `stampLastFireAt` records a schedule's manual fire
14
+ * (domain/schedule/trigger.ts). Never a load-then-save of the whole row:
15
+ * the credential's own update chains replace the row whole, and a stale
16
+ * copy written back by a stamp would undo them (a rotated PlatformClient
17
+ * secret would work again).
18
+ *
19
+ * The resolution keeps a credential that rides every request from costing
20
+ * a write per request, and it needs no state: the caller already holds the
21
+ * row it just verified, so "stamped less than a resolution ago" is one
22
+ * comparison with the stored value, the same answer on every replica and
23
+ * after every restart. Callers that pass the check at the same instant
24
+ * each write once; every write only moves the stamp forward, so the extra
25
+ * writes change nothing.
26
+ */
27
+ import type { Timestamp } from "@bufbuild/protobuf/wkt";
28
+ import { timestampDate, timestampFromDate } from "@bufbuild/protobuf/wkt";
29
+
30
+ import type { ApiResourceAudit } from "@stigmer/protos/ai/stigmer/commons/apiresource/status_pb";
31
+
32
+ import type { Logger } from "../boot/logger.js";
33
+ import { bumpStatusAudit } from "../pipeline/steps/defaults.js";
34
+ import { ResourceNotFoundError } from "../store/interface.js";
35
+
36
+ /**
37
+ * How stale a stored last use may be before a new use records again: one
38
+ * minute. Fresh enough that the console's "Used 3m" is true to the minute;
39
+ * coarse enough that a key behind every request costs at most one write a
40
+ * minute per replica. Both fields' proto comments state it.
41
+ */
42
+ export const LAST_USED_RESOLUTION_MS = 60_000;
43
+
44
+ /** The status shape both credential kinds share. */
45
+ export interface CredentialUseStatus {
46
+ lastUsedAt?: Timestamp;
47
+ audit?: ApiResourceAudit;
48
+ }
49
+
50
+ /** True when a use at `now` should be recorded over the stored `lastUsedAt`. */
51
+ export function lastUseIsStale(
52
+ lastUsedAt: Timestamp | undefined,
53
+ now: Date,
54
+ ): boolean {
55
+ return (
56
+ lastUsedAt === undefined ||
57
+ now.getTime() - timestampDate(lastUsedAt).getTime() >=
58
+ LAST_USED_RESOLUTION_MS
59
+ );
60
+ }
61
+
62
+ /**
63
+ * Records a use at `usedAt`, forward only: a stored stamp at or after it (a
64
+ * replica whose clock runs ahead, a racing caller that wrote first) is
65
+ * kept, and the audit is left alone. Returns whether it wrote. Synchronous,
66
+ * because it runs inside an atomic write's `modify`.
67
+ */
68
+ export function stampLastUsed(
69
+ status: CredentialUseStatus,
70
+ usedAt: Date,
71
+ ): boolean {
72
+ const stored = status.lastUsedAt;
73
+ if (
74
+ stored !== undefined &&
75
+ timestampDate(stored).getTime() >= usedAt.getTime()
76
+ ) {
77
+ return false;
78
+ }
79
+ status.lastUsedAt = timestampFromDate(usedAt);
80
+ bumpStatusAudit(status);
81
+ return true;
82
+ }
83
+
84
+ export interface CredentialUse {
85
+ /** What the log calls the credential ("api key", "platform client"). */
86
+ readonly credential: string;
87
+ /** The credential's resource id: logged, never the secret. */
88
+ readonly id: string;
89
+ /** The stored stamp, as the caller read the row it just verified. */
90
+ readonly lastUsedAt: Timestamp | undefined;
91
+ readonly now: Date;
92
+ readonly logger: Logger;
93
+ /**
94
+ * The kind's atomic write: applies `stamp` to the row's status (created
95
+ * when absent) inside one read-modify-write. A row deleted since the
96
+ * caller read it is a typed not-found or a write that finds nothing.
97
+ */
98
+ readonly write: (
99
+ stamp: (status: CredentialUseStatus) => void,
100
+ ) => Promise<unknown>;
101
+ }
102
+
103
+ /**
104
+ * Records the use when the stored stamp is stale. Never throws: the
105
+ * credential already did its job, and a bookkeeping failure must not turn
106
+ * an accepted request or a minted token into an error. A row deleted
107
+ * meanwhile has nothing to record; any other failure is logged.
108
+ */
109
+ export async function recordCredentialUse(use: CredentialUse): Promise<void> {
110
+ if (!lastUseIsStale(use.lastUsedAt, use.now)) {
111
+ return;
112
+ }
113
+ try {
114
+ await use.write((status) => {
115
+ stampLastUsed(status, use.now);
116
+ });
117
+ } catch (error) {
118
+ if (error instanceof ResourceNotFoundError) {
119
+ return;
120
+ }
121
+ use.logger.warn(
122
+ "Credential last_used_at not recorded (best-effort — the request is unaffected)",
123
+ {
124
+ credential: use.credential,
125
+ id: use.id,
126
+ error: error instanceof Error ? error.message : String(error),
127
+ },
128
+ );
129
+ }
130
+ }
@@ -15,6 +15,12 @@
15
15
  * SpecAudit for definition changes (search recency, version "pushed at"),
16
16
  * StatusAudit for operational changes (Recents, lifecycle metadata).
17
17
  *
18
+ * A write the PLATFORM makes on its own (a schedule's clock, a credential's
19
+ * last-use stamp) takes the narrower `bumpStatusAudit` instead: status-audit
20
+ * updated_at + event only, never an actor, because no caller made it (Go's
21
+ * clock bump, "the same two leaves every cloud runtime patch bumps"). Two
22
+ * flavors, two writers, both wire-visible — keep them distinct.
23
+ *
18
24
  * This module is also the one home of how an id is SPELLED. `generateId`
19
25
  * mints `{prefix}_{ulid}`; `derivedId` is its sibling for the kinds
20
26
  * metadata.proto names as deriving their id from a natural key instead
@@ -175,6 +181,27 @@ export function setAuditFieldsForUpdate(
175
181
  );
176
182
  }
177
183
 
184
+ /**
185
+ * Stamps the status-audit slot for a write the platform makes on its own —
186
+ * updated_at + event "updated", nothing else (see the module header for why
187
+ * this is not setAuditFieldsForUpdate). Like that helper it SETS a newly
188
+ * allocated slot rather than assigning into the existing one (#540); the
189
+ * slot's created_by/created_at/updated_by carry over unchanged.
190
+ */
191
+ export function bumpStatusAudit(status: { audit?: ApiResourceAudit }): void {
192
+ if (status.audit === undefined) {
193
+ status.audit = create(ApiResourceAuditSchema);
194
+ }
195
+ const prior = status.audit.statusAudit;
196
+ const next =
197
+ prior === undefined
198
+ ? create(ApiResourceAuditInfoSchema)
199
+ : clone(ApiResourceAuditInfoSchema, prior);
200
+ next.updatedAt = timestampNow();
201
+ next.event = "updated";
202
+ status.audit.statusAudit = next;
203
+ }
204
+
178
205
  // Operator identity (stigmer/stigmer#400): installed once at boot — before
179
206
  // any request — and read by the identity chassis when it mints the
180
207
  // trusted-local CallerIdentity (O2; audit stamping now derives its actor
@@ -20,6 +20,7 @@ import type { Schedule } from "@stigmer/protos/ai/stigmer/agentic/schedule/v1/ap
20
20
  import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
21
21
 
22
22
  import type { Logger } from "../../boot/logger.js";
23
+ import { bumpStatusAudit } from "../../pipeline/steps/defaults.js";
23
24
  import { ResourceNotFoundError } from "../../store/interface.js";
24
25
  import type { Store } from "../../store/interface.js";
25
26
  import type { ScheduleTemporalConfig } from "./config.js";
@@ -64,7 +65,7 @@ import {
64
65
  } from "./run-ledger.js";
65
66
  import type { RunStarter } from "./run-starter.js";
66
67
  import type { ScheduleSyncer } from "./syncer.js";
67
- import { bumpStatusAudit, ensureStatus } from "./status-writes.js";
68
+ import { ensureStatus } from "./status-writes.js";
68
69
 
69
70
  export interface ScheduleTickActivityDeps {
70
71
  readonly store: Store;
@@ -49,12 +49,13 @@ import type { Logger } from "../../boot/logger.js";
49
49
  import type { CallerIdentity } from "../../extensions/identity.js";
50
50
  import type { ScheduleFireCallerMint } from "../../extensions/schedule-fire-caller.js";
51
51
  import { ScheduleFireCallerRefusedError } from "../../extensions/schedule-fire-caller.js";
52
+ import { bumpStatusAudit } from "../../pipeline/steps/defaults.js";
52
53
  import { findResourceBySlug } from "../../pipeline/steps/helpers.js";
53
54
  import { ResourceNotFoundError } from "../../store/interface.js";
54
55
  import type { Store } from "../../store/interface.js";
55
56
  import type { ScheduleTemporalConfig } from "./config.js";
56
57
  import { scheduleModelPinningRefusal } from "./model-pinning.js";
57
- import { ensureStatus, bumpStatusAudit } from "./status-writes.js";
58
+ import { ensureStatus } from "./status-writes.js";
58
59
 
59
60
  /**
60
61
  * The scheduled session's pinned subject prefix — cross-edition contract
@@ -1,26 +1,19 @@
1
1
  /**
2
- * The clock's status-write helpers — port the ensureStatus/bumpStatusAudit
3
- * pair of pkg/domain/schedule/temporal/syncer.go.
2
+ * The clock's status-write helper — ports ensureStatus of
3
+ * pkg/domain/schedule/temporal/syncer.go.
4
4
  *
5
- * bumpStatusAudit is DELIBERATELY narrower than the shared
6
- * setAuditFieldsForUpdate: Go's clock stamps only status-audit
7
- * updated_at + event ("the same two leaves every cloud runtime patch
8
- * bumps") and never an actor — the clock is the platform, not an operator.
9
- * Controller-side status writes (trigger's last_fire_at stamp, resume's
10
- * clear) use the FULL shared helper instead, exactly as Go splits
11
- * steps.SetAuditFieldsForUpdate from the clock's bump. Two flavors, two
12
- * writers, both wire-visible — keep them distinct.
5
+ * The clock's audit stamp is the shared `bumpStatusAudit`
6
+ * (pipeline/steps/defaults.ts): status-audit updated_at + event only, never
7
+ * an actor — the clock is the platform, not an operator. Controller-side
8
+ * status writes (trigger's last_fire_at stamp, resume's clear) use the FULL
9
+ * setAuditFieldsForUpdate instead, exactly as Go splits
10
+ * steps.SetAuditFieldsForUpdate from the clock's bump.
13
11
  */
14
12
  import { create } from "@bufbuild/protobuf";
15
- import { timestampNow } from "@bufbuild/protobuf/wkt";
16
13
 
17
14
  import type { Schedule } from "@stigmer/protos/ai/stigmer/agentic/schedule/v1/api_pb";
18
15
  import { ScheduleStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/schedule/v1/status_pb";
19
16
  import type { ScheduleStatus } from "@stigmer/protos/ai/stigmer/agentic/schedule/v1/status_pb";
20
- import {
21
- ApiResourceAuditInfoSchema,
22
- ApiResourceAuditSchema,
23
- } from "@stigmer/protos/ai/stigmer/commons/apiresource/status_pb";
24
17
 
25
18
  /**
26
19
  * Returns the schedule's status, materializing the nested message a fresh
@@ -32,19 +25,3 @@ export function ensureStatus(schedule: Schedule): ScheduleStatus {
32
25
  }
33
26
  return schedule.status;
34
27
  }
35
-
36
- /**
37
- * Stamps the status-audit trail for a runtime write — updated_at + event
38
- * "updated", nothing else (Go bumpStatusAudit; see the module header for
39
- * why this is not setAuditFieldsForUpdate).
40
- */
41
- export function bumpStatusAudit(status: ScheduleStatus): void {
42
- if (status.audit === undefined) {
43
- status.audit = create(ApiResourceAuditSchema);
44
- }
45
- if (status.audit.statusAudit === undefined) {
46
- status.audit.statusAudit = create(ApiResourceAuditInfoSchema);
47
- }
48
- status.audit.statusAudit.updatedAt = timestampNow();
49
- status.audit.statusAudit.event = "updated";
50
- }
@@ -34,12 +34,13 @@ import type { Schedule } from "@stigmer/protos/ai/stigmer/agentic/schedule/v1/ap
34
34
  import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
35
35
 
36
36
  import type { Logger } from "../../boot/logger.js";
37
+ import { bumpStatusAudit } from "../../pipeline/steps/defaults.js";
37
38
  import { ResourceNotFoundError } from "../../store/interface.js";
38
39
  import type { Store } from "../../store/interface.js";
39
40
  import type { ScheduleArtifact } from "./artifact.js";
40
41
  import { desiredPaused, note } from "./artifact.js";
41
42
  import { artifactId } from "./names.js";
42
- import { bumpStatusAudit, ensureStatus } from "./status-writes.js";
43
+ import { ensureStatus } from "./status-writes.js";
43
44
 
44
45
  /**
45
46
  * The syncer's answer when no Temporal client exists right now (Go