@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 +17 -0
- package/package.json +6 -6
- package/src/audit-gate.ts +16 -2
- package/src/json-schema.ts +17 -6
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": "
|
|
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": "
|
|
35
|
-
"@ultimat3/core": "
|
|
36
|
-
"@ultimat3/http": "
|
|
37
|
-
"@ultimat3/policy": "
|
|
38
|
-
"@ultimat3/schema": "
|
|
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
|
-
/**
|
|
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
|
-
|
|
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 {
|
package/src/json-schema.ts
CHANGED
|
@@ -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
|
|
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
|
|
28
|
+
return normalizeJsonSchema(() => toMcpInputSchema(schema));
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
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
|
|
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
|
|