@ultimat3/action 2.0.0 → 3.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/CLAUDE.md CHANGED
@@ -332,6 +332,23 @@ Owns the `action` + `mutator` primitives and their six projections. Tier 3.
332
332
  `X_AUDIT_SINK_FAILED` fell into the failure branch and wrote a *second* record saying the
333
333
  action `failed` — for a handler that had committed. An audit trail lying about a write is
334
334
  worse than no audit trail; `audit.test.ts` counts the records a refusing sink was offered.
335
+ - **`auditOutcomeFor` is TOTAL, because both callers ask inside a `catch`.**
336
+ `error instanceof ActionDeniedError` runs a `Proxy`'s `getPrototypeOf` trap, and the two call
337
+ sites are `execute`'s span attribute and the one place the `failed` record is produced — so a
338
+ handler throwing such a value made the probe throw *from the frame holding the app's error*,
339
+ and the caller got a `TypeError` in place of its own throwable. It fails closed to `failed`: a
340
+ value that refuses to be examined is not evidence of a policy denial. Same rule as core's
341
+ `isThrownError` / `isUltimateError`, and the same defect `@ultimat3/http`'s `finalize.ts` and
342
+ `factsOf` carried; `audit.test.ts` asserts the caller's throwable by IDENTITY, which is the only
343
+ assertion that catches a replacement.
344
+ - **`json-schema.ts`'s refusal names `introspect()`, never a `toJsonSchema` member.**
345
+ `SchemaProvider` declares no such member and `toJsonSchema()` calls `introspect()`
346
+ unconditionally, so the old `fix:` told a reader to implement an API that does not exist —
347
+ axiom 4 inverted, and invisible to `x verify`'s `errors` step, which checks a fix line's shape
348
+ and never whether the API it names is real. The guard is `normalizeJsonSchema`, exported from
349
+ the module for its own test and absent from `src/index.ts` exactly as `sortSchema` is; the
350
+ shipped `toJsonSchema` cannot reach it today (it returns an object literal on every path), so
351
+ the test drives the guard directly rather than pretending a converter can be swapped.
335
352
  - App code reaches a projection through the action (`publishPost.tool()`), never through
336
353
  `.def` and never by importing the projection function. `facade.ts` is where a new method
337
354
  is bound; the projection itself keeps living in its own file.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/action",
3
- "version": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "The action primitive: one declaration projected to route, OpenAPI, client, MCP tool, job handle, tests",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,10 +31,10 @@
31
31
  "test": "bun test"
32
32
  },
33
33
  "dependencies": {
34
- "@ultimat3/cache": "2.0.0",
35
- "@ultimat3/core": "2.0.0",
36
- "@ultimat3/http": "2.0.0",
37
- "@ultimat3/policy": "2.0.0",
38
- "@ultimat3/schema": "2.0.0"
34
+ "@ultimat3/cache": "3.0.0",
35
+ "@ultimat3/core": "3.0.0",
36
+ "@ultimat3/http": "3.0.0",
37
+ "@ultimat3/policy": "3.0.0",
38
+ "@ultimat3/schema": "3.0.0"
39
39
  }
40
40
  }
package/src/audit-gate.ts CHANGED
@@ -20,9 +20,23 @@ export function auditSinkFor(action: string): AuditSink {
20
20
  return sink;
21
21
  }
22
22
 
23
- /** An authz refusal is `denied`; everything else that threw is `failed`, an unparsed input included. */
23
+ /**
24
+ * An authz refusal is `denied`; everything else that threw is `failed`, an unparsed input
25
+ * included.
26
+ *
27
+ * TOTAL, the same rule core's `isThrownError` states: `instanceof` runs a `Proxy`'s
28
+ * `getPrototypeOf` trap, and both callers ask this question inside a `catch` that is still
29
+ * holding the app's own error — `execute`'s span attribute and the one place the `failed` record
30
+ * is produced. A probe that threw there REPLACED the caller's throwable with its own `TypeError`,
31
+ * so an app catching by `instanceof` upstream stopped matching. A value that refuses to be
32
+ * examined is not evidence of a policy denial, so it fails closed to `failed`.
33
+ */
24
34
  export function auditOutcomeFor(error: unknown): AuditOutcome {
25
- return error instanceof ActionDeniedError ? 'denied' : 'failed';
35
+ try {
36
+ return error instanceof ActionDeniedError ? 'denied' : 'failed';
37
+ } catch {
38
+ return 'failed';
39
+ }
26
40
  }
27
41
 
28
42
  export function auditFailureFor(error: unknown): AuditFailure {
@@ -20,22 +20,33 @@ export type JsonSchemaObject = Record<string, unknown>;
20
20
  * reach a projection that throws.
21
21
  */
22
22
  export function jsonSchemaOf(schema: StandardSchemaV1): JsonSchemaObject {
23
- return normalize(() => toJsonSchema(schema));
23
+ return normalizeJsonSchema(() => toJsonSchema(schema));
24
24
  }
25
25
 
26
26
  /** Draft-07, no `$schema` — the exact shape an MCP `tools/list` entry needs. */
27
27
  export function mcpSchemaOf(schema: StandardSchemaV1): JsonSchemaObject {
28
- return normalize(() => toMcpInputSchema(schema));
28
+ return normalizeJsonSchema(() => toMcpInputSchema(schema));
29
29
  }
30
30
 
31
- function normalize(convert: () => unknown): JsonSchemaObject {
31
+ /**
32
+ * The narrowing both projections share, and the refusal it earns: a converter that answered with
33
+ * something that is not a JSON object is the same failure by a quieter route, so it gets the same
34
+ * shipped code rather than a permissive node. Exported for its own test and nothing else — it is
35
+ * absent from `src/index.ts`, exactly as `sortSchema` is.
36
+ *
37
+ * **The `fix:` names `introspect`, never a `toJsonSchema` member.** `SchemaProvider` declares no
38
+ * such member (`packages/schema/src/provider.ts`) and `toJsonSchema()` calls `introspect()`
39
+ * unconditionally, so the old line — "configure a provider whose toJsonSchema returns an object" —
40
+ * instructed a reader to implement an API that does not exist, which is axiom 4 inverted. It is
41
+ * the same false clause `@ultimat3/schema` deleted from its own docs; this was the user-visible
42
+ * half, one package over.
43
+ */
44
+ export function normalizeJsonSchema(convert: () => unknown): JsonSchemaObject {
32
45
  const raw: unknown = convert();
33
46
  if (isJsonObject(raw)) return raw;
34
- // A converter that answered with something that is not a JSON object is the same failure by a
35
- // quieter route, so it gets the same shipped code rather than a permissive node.
36
47
  throw new SchemaUnsupportedError({
37
48
  cause: `the schema converted to ${raw === null ? 'null' : typeof raw}, not a JSON Schema object`,
38
- fix: 'declare the schema with `t` from @ultimat3/action, or configure a provider whose toJsonSchema returns an object: configureSchemaProvider({ ... })',
49
+ fix: 'declare the schema with `t` from @ultimat3/action, or add an introspect() returning a SchemaNode to the object passed to configureSchemaProvider()',
39
50
  });
40
51
  }
41
52