@stigmer/server 3.38.2 → 3.38.3

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 (40) hide show
  1. package/dist/domain/agentexecution/controller.js +7 -3
  2. package/dist/domain/agentexecution/controller.js.map +1 -1
  3. package/dist/domain/agentexecution/update-status.d.ts +9 -2
  4. package/dist/domain/agentexecution/update-status.d.ts.map +1 -1
  5. package/dist/domain/agentexecution/update-status.js +28 -5
  6. package/dist/domain/agentexecution/update-status.js.map +1 -1
  7. package/dist/domain/agentexecution/validate-thinking-mode.d.ts +31 -21
  8. package/dist/domain/agentexecution/validate-thinking-mode.d.ts.map +1 -1
  9. package/dist/domain/agentexecution/validate-thinking-mode.js +61 -18
  10. package/dist/domain/agentexecution/validate-thinking-mode.js.map +1 -1
  11. package/dist/domain/artifact/controller.d.ts.map +1 -1
  12. package/dist/domain/artifact/controller.js +9 -6
  13. package/dist/domain/artifact/controller.js.map +1 -1
  14. package/dist/domain/environment/controller.d.ts +2 -2
  15. package/dist/domain/environment/controller.js +7 -3
  16. package/dist/domain/environment/controller.js.map +1 -1
  17. package/dist/domain/environment/steps.d.ts +2 -1
  18. package/dist/domain/environment/steps.d.ts.map +1 -1
  19. package/dist/domain/environment/steps.js +10 -7
  20. package/dist/domain/environment/steps.js.map +1 -1
  21. package/dist/domain/workflow/registry/data/model-registry.json +23 -14
  22. package/dist/domain/workflow/registry/model-registry-store.d.ts +14 -3
  23. package/dist/domain/workflow/registry/model-registry-store.d.ts.map +1 -1
  24. package/dist/domain/workflow/registry/model-registry-store.js +14 -3
  25. package/dist/domain/workflow/registry/model-registry-store.js.map +1 -1
  26. package/package.json +6 -6
  27. package/src/domain/agentexecution/__tests__/run-gate.test.ts +42 -1
  28. package/src/domain/agentexecution/__tests__/update-status.test.ts +58 -2
  29. package/src/domain/agentexecution/__tests__/validate-thinking-mode.test.ts +209 -61
  30. package/src/domain/agentexecution/controller.ts +7 -3
  31. package/src/domain/agentexecution/update-status.ts +43 -3
  32. package/src/domain/agentexecution/validate-thinking-mode.ts +102 -47
  33. package/src/domain/artifact/__tests__/store-faults.test.ts +117 -0
  34. package/src/domain/artifact/controller.ts +12 -6
  35. package/src/domain/environment/__tests__/store-faults.test.ts +150 -0
  36. package/src/domain/environment/controller.ts +8 -5
  37. package/src/domain/environment/steps.ts +10 -7
  38. package/src/domain/workflow/registry/data/model-registry.json +23 -14
  39. package/src/domain/workflow/registry/model-registry-store.ts +16 -3
  40. package/src/pipeline/__tests__/blind-not-found.test.ts +0 -3
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Pins the store-fault contract the artifact load by id shares: a typed
3
+ * ResourceNotFoundError answers NotFound with the domain's pinned Go copy
4
+ * (`Artifact not found: <id>`), and any other store failure is an
5
+ * infrastructure fault answered as a sanitized Internal, never a NotFound
6
+ * that tells a client a run's output does not exist (stigmer/stigmer#1345).
7
+ *
8
+ * The surfaces: the three direct handlers that load through
9
+ * loadArtifactOrNotFound, delete, getDownloadUrl and getContent, each
10
+ * reached through the registered handler on an in-process router. All three
11
+ * load before they authorize (stigmer#224), and a fault answered as Internal
12
+ * says nothing about whether the artifact exists. The composed suites reach
13
+ * only a real store, which cannot fail selectively, so each surface runs here
14
+ * against a store whose read throws. Artifact storage is untouchable: a load
15
+ * that fails must stop the call before any blob read or URL mint.
16
+ *
17
+ * Out of scope: the NotFound copy itself (wire contract, pinned by the
18
+ * artifact conformance suite) and create's best-effort org derivation
19
+ * (stigmer/stigmer#1369).
20
+ */
21
+ import type { Client, ConnectError } from "@connectrpc/connect";
22
+ import { Code, createClient, createRouterTransport } from "@connectrpc/connect";
23
+ import { describe, expect, it } from "vitest";
24
+
25
+ import { ArtifactCommandController } from "@stigmer/protos/ai/stigmer/agentic/artifact/v1/command_pb";
26
+ import { ArtifactQueryController } from "@stigmer/protos/ai/stigmer/agentic/artifact/v1/query_pb";
27
+
28
+ import { createLogger } from "../../../boot/logger.js";
29
+ import { createVerifierChainInterceptor } from "../../../pipeline/interceptors/auth.js";
30
+ import {
31
+ errorOf,
32
+ failingStore,
33
+ untouchable,
34
+ } from "../../../pipeline/__tests__/support.js";
35
+ import { newPermissiveSingleTeamAuthorizer } from "../../../pipeline/steps/authorize.js";
36
+ import { ResourceNotFoundError } from "../../../store/interface.js";
37
+ import type { Store } from "../../../store/interface.js";
38
+
39
+ import { registerArtifactServices } from "../controller.js";
40
+
41
+ const silentLogger = createLogger({
42
+ level: "error",
43
+ pretty: false,
44
+ write: () => {},
45
+ });
46
+
47
+ const ARTIFACT_ID = "art_storefault";
48
+
49
+ const NOT_FOUND_COPY = `Artifact not found: ${ARTIFACT_ID}`;
50
+ const LOAD_FAULT_COPY = "failed to load artifact";
51
+
52
+ const MISSING = (): Error =>
53
+ new ResourceNotFoundError(`artifact/${ARTIFACT_ID}`);
54
+ const LOCKED = (): Error => new Error("SQLITE_BUSY: database is locked");
55
+
56
+ interface ArtifactClients {
57
+ readonly command: Client<typeof ArtifactCommandController>;
58
+ readonly query: Client<typeof ArtifactQueryController>;
59
+ }
60
+
61
+ /**
62
+ * The registered handlers on an in-process router (`artifact.test.ts`'s
63
+ * harness): the verifier chain stamps the trusted-local caller, each handler
64
+ * checks its id, and the load is the first read of the store.
65
+ */
66
+ function artifactClients(store: Store): ArtifactClients {
67
+ const transport = createRouterTransport(
68
+ (router) => {
69
+ registerArtifactServices(router, {
70
+ store,
71
+ artifactStorage: untouchable("artifactStorage"),
72
+ logger: silentLogger,
73
+ authorizer: newPermissiveSingleTeamAuthorizer(),
74
+ });
75
+ },
76
+ {
77
+ router: {
78
+ interceptors: [createVerifierChainInterceptor([], [], silentLogger)],
79
+ },
80
+ },
81
+ );
82
+ return {
83
+ command: createClient(ArtifactCommandController, transport),
84
+ query: createClient(ArtifactQueryController, transport),
85
+ };
86
+ }
87
+
88
+ const SURFACES: ReadonlyArray<
89
+ readonly [string, (clients: ArtifactClients) => Promise<unknown>]
90
+ > = [
91
+ ["delete", ({ command }) => command.delete({ value: ARTIFACT_ID })],
92
+ [
93
+ "getDownloadUrl",
94
+ ({ query }) => query.getDownloadUrl({ value: ARTIFACT_ID }),
95
+ ],
96
+ ["getContent", ({ query }) => query.getContent({ artifactId: ARTIFACT_ID })],
97
+ ];
98
+
99
+ describe.each(SURFACES)("%s — loadArtifactOrNotFound", (_name, call) => {
100
+ function surfaceError(store: Store): Promise<ConnectError> {
101
+ return errorOf(() => call(artifactClients(store)));
102
+ }
103
+
104
+ it("a missing artifact answers NotFound with the domain's copy", async () => {
105
+ const error = await surfaceError(failingStore(MISSING()));
106
+
107
+ expect(error.code).toBe(Code.NotFound);
108
+ expect(error.rawMessage).toBe(NOT_FOUND_COPY);
109
+ });
110
+
111
+ it("any other store failure answers a sanitized Internal", async () => {
112
+ const error = await surfaceError(failingStore(LOCKED()));
113
+
114
+ expect(error.code).toBe(Code.Internal);
115
+ expect(error.rawMessage).toBe(LOAD_FAULT_COPY);
116
+ });
117
+ });
@@ -9,7 +9,8 @@
9
9
  * storage_state transition rather than the hard-delete pipeline; only
10
10
  * get/listByExecution ride pipelines. Proven by
11
11
  * artifact.conformance.test.ts (CONFORMANCE_TARGET=local, incl. the
12
- * file-server lane) and __tests__/artifact.test.ts.
12
+ * file-server lane), __tests__/artifact.test.ts and
13
+ * __tests__/store-faults.test.ts.
13
14
  *
14
15
  * Go tolerates a nil ArtifactStorage (two-phase wiring) and answers
15
16
  * Internal "artifact storage not configured"; the TS composition root
@@ -79,6 +80,7 @@ import {
79
80
  newLoadTargetStep,
80
81
  } from "../../pipeline/steps/load-target.js";
81
82
  import { newValidateProtoStep } from "../../pipeline/steps/validation.js";
83
+ import { ResourceNotFoundError } from "../../store/interface.js";
82
84
  import type { Store } from "../../store/interface.js";
83
85
  import type { ListIndexRow } from "../../store/list-index.js";
84
86
  import { artifactListIndex } from "./list-index.js";
@@ -649,7 +651,7 @@ async function getContent(
649
651
  });
650
652
  }
651
653
 
652
- /** Load by id with the byte-pinned Go NotFound copy. */
654
+ /** Load by id: a missing artifact answers the byte-pinned Go NotFound copy, a store fault Internal. */
653
655
  async function loadArtifactOrNotFound(
654
656
  deps: ArtifactControllerDeps,
655
657
  artifactId: string,
@@ -660,10 +662,14 @@ async function loadArtifactOrNotFound(
660
662
  artifactId,
661
663
  ArtifactSchema,
662
664
  );
663
- } catch {
664
- // Go answers NotFound for ANY store failure here (status.Errorf on
665
- // err != nil), not only missing rows — mirrored.
666
- throw new ConnectError(artifactNotFoundMessage(artifactId), Code.NotFound);
665
+ } catch (error) {
666
+ if (error instanceof ResourceNotFoundError) {
667
+ throw new ConnectError(
668
+ artifactNotFoundMessage(artifactId),
669
+ Code.NotFound,
670
+ );
671
+ }
672
+ throw internalError(error, "failed to load artifact");
667
673
  }
668
674
  }
669
675
 
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Pins the store-fault contract every environment load by id shares: a typed
3
+ * ResourceNotFoundError answers NotFound with the domain's pinned copy
4
+ * (`environment not found: <id>`), and any other store failure is an
5
+ * infrastructure fault answered as a sanitized Internal, never a NotFound
6
+ * that tells a client the environment does not exist
7
+ * (stigmer/stigmer#1345).
8
+ *
9
+ * The surfaces: updateVisibility (its own load step) and the three RPCs that
10
+ * share LoadEnvironmentByID, updateVariables, removeVariables and
11
+ * getSecretValue, each reached through the registered handler on an
12
+ * in-process router. The composed suites reach only a real store, which
13
+ * cannot fail selectively, so each surface runs here against a store whose
14
+ * read throws. The secret service is untouchable: a load that fails must stop
15
+ * the call before any decrypt or encrypt.
16
+ *
17
+ * Out of scope: the NotFound copy itself (wire contract, pinned by
18
+ * environment.test.ts and the environment conformance suite) and the
19
+ * personal-environment resolver's fold of a failed secret read into a
20
+ * missing key (stigmer/stigmer#1360).
21
+ */
22
+ import type { Client, ConnectError } from "@connectrpc/connect";
23
+ import { Code, createClient, createRouterTransport } from "@connectrpc/connect";
24
+ import { describe, expect, it } from "vitest";
25
+
26
+ import { EnvironmentCommandController } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/command_pb";
27
+ import { EnvironmentQueryController } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/query_pb";
28
+ import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
29
+
30
+ import { createLogger } from "../../../boot/logger.js";
31
+ import { createApiResourceInterceptor } from "../../../pipeline/interceptors/apiresource.js";
32
+ import { createVerifierChainInterceptor } from "../../../pipeline/interceptors/auth.js";
33
+ import {
34
+ errorOf,
35
+ failingStore,
36
+ untouchable,
37
+ } from "../../../pipeline/__tests__/support.js";
38
+ import { newPermissiveSingleTeamAuthorizer } from "../../../pipeline/steps/authorize.js";
39
+ import { ResourceNotFoundError } from "../../../store/interface.js";
40
+ import type { Store } from "../../../store/interface.js";
41
+
42
+ import { registerEnvironmentServices } from "../controller.js";
43
+
44
+ const silentLogger = createLogger({
45
+ level: "error",
46
+ pretty: false,
47
+ write: () => {},
48
+ });
49
+
50
+ const ENVIRONMENT_ID = "env_storefault";
51
+
52
+ const NOT_FOUND_COPY = `environment not found: ${ENVIRONMENT_ID}`;
53
+ const LOAD_FAULT_COPY = "failed to load environment";
54
+
55
+ const MISSING = (): Error =>
56
+ new ResourceNotFoundError(`environment/${ENVIRONMENT_ID}`);
57
+ const LOCKED = (): Error => new Error("SQLITE_BUSY: database is locked");
58
+
59
+ interface EnvironmentClients {
60
+ readonly command: Client<typeof EnvironmentCommandController>;
61
+ readonly query: Client<typeof EnvironmentQueryController>;
62
+ }
63
+
64
+ /**
65
+ * The registered handlers on an in-process router: the verifier chain stamps
66
+ * the trusted-local caller and the apiresource interceptor the kind both
67
+ * loads read, then Authorize and ValidateProto run and the load is the first
68
+ * step that reads the store.
69
+ */
70
+ function environmentClients(store: Store): EnvironmentClients {
71
+ const transport = createRouterTransport(
72
+ (router) => {
73
+ registerEnvironmentServices(router, {
74
+ store,
75
+ logger: silentLogger,
76
+ authorizer: newPermissiveSingleTeamAuthorizer(),
77
+ authorizationLifecycle: undefined,
78
+ secretService: untouchable("secretService"),
79
+ listReadScope: undefined,
80
+ });
81
+ },
82
+ {
83
+ router: {
84
+ interceptors: [
85
+ createVerifierChainInterceptor([], [], silentLogger),
86
+ createApiResourceInterceptor(),
87
+ ],
88
+ },
89
+ },
90
+ );
91
+ return {
92
+ command: createClient(EnvironmentCommandController, transport),
93
+ query: createClient(EnvironmentQueryController, transport),
94
+ };
95
+ }
96
+
97
+ /** Each surface with an input valid enough to pass ValidateProto. */
98
+ const SURFACES: ReadonlyArray<
99
+ readonly [string, (clients: EnvironmentClients) => Promise<unknown>]
100
+ > = [
101
+ [
102
+ "updateVisibility — LoadEnvironmentForVisibilityUpdate",
103
+ ({ command }) =>
104
+ command.updateVisibility({
105
+ resourceId: ENVIRONMENT_ID,
106
+ visibility: ApiResourceVisibility.visibility_private,
107
+ }),
108
+ ],
109
+ [
110
+ "updateVariables — LoadEnvironmentByID",
111
+ ({ command }) =>
112
+ command.updateVariables({
113
+ environmentId: ENVIRONMENT_ID,
114
+ variables: { API_KEY: { value: "v", isSecret: false } },
115
+ }),
116
+ ],
117
+ [
118
+ "removeVariables — LoadEnvironmentByID",
119
+ ({ command }) =>
120
+ command.removeVariables({
121
+ environmentId: ENVIRONMENT_ID,
122
+ keys: ["API_KEY"],
123
+ }),
124
+ ],
125
+ [
126
+ "getSecretValue — LoadEnvironmentByID",
127
+ ({ query }) =>
128
+ query.getSecretValue({ environmentId: ENVIRONMENT_ID, key: "API_KEY" }),
129
+ ],
130
+ ];
131
+
132
+ describe.each(SURFACES)("%s", (_name, call) => {
133
+ function surfaceError(store: Store): Promise<ConnectError> {
134
+ return errorOf(() => call(environmentClients(store)));
135
+ }
136
+
137
+ it("a missing environment answers NotFound with the domain's copy", async () => {
138
+ const error = await surfaceError(failingStore(MISSING()));
139
+
140
+ expect(error.code).toBe(Code.NotFound);
141
+ expect(error.rawMessage).toBe(NOT_FOUND_COPY);
142
+ });
143
+
144
+ it("any other store failure answers a sanitized Internal", async () => {
145
+ const error = await surfaceError(failingStore(LOCKED()));
146
+
147
+ expect(error.code).toBe(Code.Internal);
148
+ expect(error.rawMessage).toBe(LOAD_FAULT_COPY);
149
+ });
150
+ });
@@ -8,8 +8,8 @@
8
8
  * design: limited blast radius, per-key audit, the industry "reveal" UX).
9
9
  *
10
10
  * Pipeline per RPC mirrors the Go step chains character-for-character.
11
- * Proven by environment.conformance.test.ts (CONFORMANCE_TARGET=local)
12
- * and __tests__/environment.test.ts.
11
+ * Proven by environment.conformance.test.ts (CONFORMANCE_TARGET=local),
12
+ * __tests__/environment.test.ts and __tests__/store-faults.test.ts.
13
13
  *
14
14
  * Versus Stigmer Cloud, OSS excludes the Authorize, CreateIamPolicies,
15
15
  * FGA-tuple, and Publish steps (no multi-tenant auth, IAM/FGA, or event
@@ -421,7 +421,7 @@ async function updateVisibility(
421
421
  return env;
422
422
  }
423
423
 
424
- /** Loads the environment by resource_id; ANY load failure → NotFound. */
424
+ /** Loads the environment by resource_id; a missing one answers NotFound, a store fault Internal. */
425
425
  function newLoadEnvironmentForVisibilityUpdateStep(
426
426
  store: Store,
427
427
  ): PipelineStep<UpdateVisibilityDesc> {
@@ -436,8 +436,11 @@ function newLoadEnvironmentForVisibilityUpdateStep(
436
436
  input.resourceId,
437
437
  EnvironmentSchema,
438
438
  );
439
- } catch {
440
- throw notFoundError("environment", input.resourceId);
439
+ } catch (error) {
440
+ if (error instanceof ResourceNotFoundError) {
441
+ throw notFoundError("environment", input.resourceId);
442
+ }
443
+ throw internalError(error, "failed to load environment");
441
444
  }
442
445
  ctx.set(UPDATE_VISIBILITY_ENVIRONMENT_KEY, env);
443
446
  },
@@ -15,8 +15,8 @@
15
15
  * by design and must not hit the forgery rejection.
16
16
  * 3. Redaction (redact.ts) runs AFTER Persist, outside the pipeline.
17
17
  *
18
- * Proven by environment.conformance.test.ts (CONFORMANCE_TARGET=local)
19
- * and __tests__/environment.test.ts.
18
+ * Proven by environment.conformance.test.ts (CONFORMANCE_TARGET=local),
19
+ * __tests__/environment.test.ts and __tests__/store-faults.test.ts.
20
20
  */
21
21
  import { create } from "@bufbuild/protobuf";
22
22
  import type { DescMessage } from "@bufbuild/protobuf";
@@ -51,6 +51,7 @@ import { findResourceByLabelAndOrg } from "../../pipeline/steps/helpers.js";
51
51
  import { EXISTING_RESOURCE_KEY } from "../../pipeline/steps/load-existing.js";
52
52
  import { TARGET_RESOURCE_KEY } from "../../pipeline/steps/load-target.js";
53
53
  import { destroySecretBackingState } from "../../pipeline/steps/secret-cleanup.js";
54
+ import { ResourceNotFoundError } from "../../store/interface.js";
54
55
  import type { Store } from "../../store/interface.js";
55
56
  import {
56
57
  PERSONAL_LABEL_KEY,
@@ -273,7 +274,8 @@ export function newEnforcePersonalUniquenessStep(
273
274
  * environment_id field (the standard LoadTarget expects a `value` id
274
275
  * field, but EnvironmentSecretValueInput / Update-/RemoveEnvironment-
275
276
  * VariablesRequest carry `environment_id`). Stores under
276
- * TARGET_RESOURCE_KEY.
277
+ * TARGET_RESOURCE_KEY. A missing environment answers NotFound, a store
278
+ * fault Internal.
277
279
  */
278
280
  export function newLoadEnvironmentByIdStep<Desc extends DescMessage>(
279
281
  store: Store,
@@ -294,10 +296,11 @@ export function newLoadEnvironmentByIdStep<Desc extends DescMessage>(
294
296
  environmentId,
295
297
  EnvironmentSchema,
296
298
  );
297
- } catch {
298
- // Go maps EVERY load failure to NotFound here (store errors
299
- // included) — ported as-is.
300
- throw notFoundError("environment", environmentId);
299
+ } catch (error) {
300
+ if (error instanceof ResourceNotFoundError) {
301
+ throw notFoundError("environment", environmentId);
302
+ }
303
+ throw internalError(error, "failed to load environment");
301
304
  }
302
305
 
303
306
  ctx.set(TARGET_RESOURCE_KEY, env);
@@ -963,7 +963,8 @@
963
963
  "vision" : true,
964
964
  "streaming" : true,
965
965
  "thinking" : false,
966
- "adaptiveThinking" : true
966
+ "adaptiveThinking" : true,
967
+ "thinkingRequired" : true
967
968
  }
968
969
  }, {
969
970
  "id" : "claude-haiku-4.5",
@@ -995,8 +996,9 @@
995
996
  "toolUse" : true,
996
997
  "vision" : true,
997
998
  "streaming" : true,
998
- "thinking" : false,
999
- "adaptiveThinking" : false
999
+ "thinking" : true,
1000
+ "adaptiveThinking" : false,
1001
+ "thinkingRequired" : false
1000
1002
  }
1001
1003
  }, {
1002
1004
  "id" : "claude-opus-4.5",
@@ -1017,7 +1019,7 @@
1017
1019
  "effectiveAt" : "2026-05-03T00:00:00Z"
1018
1020
  },
1019
1021
  "contextWindowTokens" : 200000,
1020
- "maxOutputTokens" : 65536,
1022
+ "maxOutputTokens" : 64000,
1021
1023
  "tokenCounterMethod" : "anthropic_native",
1022
1024
  "summarization" : {
1023
1025
  "triggerThreshold" : 180000,
@@ -1029,7 +1031,8 @@
1029
1031
  "vision" : true,
1030
1032
  "streaming" : true,
1031
1033
  "thinking" : true,
1032
- "adaptiveThinking" : false
1034
+ "adaptiveThinking" : false,
1035
+ "thinkingRequired" : false
1033
1036
  }
1034
1037
  }, {
1035
1038
  "id" : "claude-opus-4.6",
@@ -1050,7 +1053,7 @@
1050
1053
  "effectiveAt" : "2026-05-03T00:00:00Z"
1051
1054
  },
1052
1055
  "contextWindowTokens" : 200000,
1053
- "maxOutputTokens" : 131072,
1056
+ "maxOutputTokens" : 128000,
1054
1057
  "tokenCounterMethod" : "anthropic_native",
1055
1058
  "summarization" : {
1056
1059
  "triggerThreshold" : 180000,
@@ -1062,7 +1065,8 @@
1062
1065
  "vision" : true,
1063
1066
  "streaming" : true,
1064
1067
  "thinking" : false,
1065
- "adaptiveThinking" : true
1068
+ "adaptiveThinking" : true,
1069
+ "thinkingRequired" : false
1066
1070
  }
1067
1071
  }, {
1068
1072
  "id" : "claude-opus-4.7",
@@ -1096,7 +1100,8 @@
1096
1100
  "vision" : true,
1097
1101
  "streaming" : true,
1098
1102
  "thinking" : false,
1099
- "adaptiveThinking" : true
1103
+ "adaptiveThinking" : true,
1104
+ "thinkingRequired" : false
1100
1105
  }
1101
1106
  }, {
1102
1107
  "id" : "claude-opus-4.8",
@@ -1130,7 +1135,8 @@
1130
1135
  "vision" : true,
1131
1136
  "streaming" : true,
1132
1137
  "thinking" : false,
1133
- "adaptiveThinking" : true
1138
+ "adaptiveThinking" : true,
1139
+ "thinkingRequired" : false
1134
1140
  }
1135
1141
  }, {
1136
1142
  "id" : "claude-sonnet-4.5",
@@ -1151,7 +1157,7 @@
1151
1157
  "effectiveAt" : "2026-05-03T00:00:00Z"
1152
1158
  },
1153
1159
  "contextWindowTokens" : 200000,
1154
- "maxOutputTokens" : 65536,
1160
+ "maxOutputTokens" : 64000,
1155
1161
  "tokenCounterMethod" : "anthropic_native",
1156
1162
  "summarization" : {
1157
1163
  "triggerThreshold" : 180000,
@@ -1163,7 +1169,8 @@
1163
1169
  "vision" : true,
1164
1170
  "streaming" : true,
1165
1171
  "thinking" : true,
1166
- "adaptiveThinking" : false
1172
+ "adaptiveThinking" : false,
1173
+ "thinkingRequired" : false
1167
1174
  }
1168
1175
  }, {
1169
1176
  "id" : "claude-sonnet-4.6",
@@ -1195,8 +1202,9 @@
1195
1202
  "toolUse" : true,
1196
1203
  "vision" : true,
1197
1204
  "streaming" : true,
1198
- "thinking" : true,
1199
- "adaptiveThinking" : false
1205
+ "thinking" : false,
1206
+ "adaptiveThinking" : true,
1207
+ "thinkingRequired" : false
1200
1208
  }
1201
1209
  }, {
1202
1210
  "id" : "claude-sonnet-5",
@@ -1230,7 +1238,8 @@
1230
1238
  "vision" : true,
1231
1239
  "streaming" : true,
1232
1240
  "thinking" : false,
1233
- "adaptiveThinking" : true
1241
+ "adaptiveThinking" : true,
1242
+ "thinkingRequired" : false
1234
1243
  }
1235
1244
  } ]
1236
1245
  }
@@ -63,6 +63,19 @@ export const FAST_VARIANT_KEY = "fast";
63
63
  */
64
64
  export const THINKING_CAPABILITY_KEY = "thinking";
65
65
 
66
+ /**
67
+ * The registry capability key of adaptive thinking, the second form that
68
+ * makes THINKING_MODE_ENABLED selectable (stigmer/stigmer#1280).
69
+ */
70
+ export const ADAPTIVE_THINKING_CAPABILITY_KEY = "adaptiveThinking";
71
+
72
+ /**
73
+ * The registry capability key naming a model that always thinks and refuses
74
+ * a request to turn thinking off: an explicit THINKING_MODE_DISABLED is
75
+ * refused for it at create.
76
+ */
77
+ export const THINKING_REQUIRED_CAPABILITY_KEY = "thinkingRequired";
78
+
66
79
  export interface ModelRegistryStoreOptions {
67
80
  /** Bundled build-time snapshot; MUST pass the sanity gate at boot. */
68
81
  bundledDocument: string;
@@ -202,9 +215,9 @@ export class ModelRegistryStore implements ModelCatalogProvider {
202
215
  * "thinking") true under the given harness. Capability flags are
203
216
  * harness-scoped facts (they describe what works through that serving
204
217
  * path), so there is no any-harness form: THINKING_MODE_ENABLED validates
205
- * against the cursor harness specifically — the only harness with a
206
- * thinking translation in v1 (oss#772) — and native entries declaring the
207
- * same capability stay unselectable until a native wire mapping exists.
218
+ * against the entry of the harness the execution runs on (oss#772,
219
+ * stigmer/stigmer#1280), because an id with an entry on both harnesses
220
+ * can declare different capabilities on each.
208
221
  */
209
222
  hasCapabilityForHarness(
210
223
  harness: string,
@@ -41,9 +41,6 @@ const PENDING: ReadonlyMap<string, number> = new Map([
41
41
  ["domain/agentchannel/controller.ts", 1],
42
42
  ["domain/agentinstance/controller.ts", 1],
43
43
  ["domain/agentshare/controller.ts", 1],
44
- ["domain/artifact/controller.ts", 1],
45
- ["domain/environment/controller.ts", 1],
46
- ["domain/environment/steps.ts", 1],
47
44
  ["domain/plugin/controller.ts", 1],
48
45
  ["domain/skill/controller.ts", 1],
49
46
  ["domain/workflow/controller.ts", 1],