@pithy-sh/core 0.12.0 → 0.13.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/dist/audit/composed.d.ts +18 -0
- package/dist/audit/composed.d.ts.map +1 -0
- package/dist/audit/composed.js +28 -0
- package/dist/capability/capability.d.ts +11 -0
- package/dist/capability/capability.d.ts.map +1 -1
- package/dist/capability/purge.d.ts +43 -1
- package/dist/capability/purge.d.ts.map +1 -1
- package/dist/capability/purge.js +58 -3
- package/dist/cli/commandCatalog.generated.d.ts +1 -1
- package/dist/cli/commandCatalog.generated.d.ts.map +1 -1
- package/dist/cli/commandCatalog.generated.js +6 -0
- package/dist/controlPlane/http/guard.d.ts.map +1 -1
- package/dist/controlPlane/http/guard.js +40 -1
- package/dist/createEntrypoint.d.ts +7 -1
- package/dist/createEntrypoint.d.ts.map +1 -1
- package/dist/createEntrypoint.js +15 -5
- package/dist/error/http.d.ts +48 -0
- package/dist/error/http.d.ts.map +1 -1
- package/dist/error/payload.d.ts +96 -0
- package/dist/error/payload.d.ts.map +1 -1
- package/dist/error/payload.js +32 -0
- package/dist/http/validation.d.ts +11 -2
- package/dist/http/validation.d.ts.map +1 -1
- package/dist/http/validation.js +20 -3
- package/dist/logger/record.d.ts +28 -0
- package/dist/logger/record.d.ts.map +1 -1
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/package.json +1 -1
- package/src/audit/composed.ts +30 -0
- package/src/capability/capability.ts +11 -0
- package/src/capability/purge.ts +66 -3
- package/src/cli/commandCatalog.generated.ts +6 -0
- package/src/controlPlane/http/guard.ts +60 -1
- package/src/createEntrypoint.ts +17 -2
- package/src/error/payload.ts +68 -0
- package/src/http/validation.ts +35 -3
- package/src/version.generated.ts +1 -1
package/dist/error/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/error/http.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAGpC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAA4B,UAAU,EAAmB,MAAM,cAAc,CAAC;AAErF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,SAAS
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/error/http.ts"],"names":[],"mappings":"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"}
|
package/dist/error/payload.d.ts
CHANGED
|
@@ -660,6 +660,26 @@ export declare const KitPublicErrorPayload: z.ZodDiscriminatedUnion<[z.ZodObject
|
|
|
660
660
|
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
661
661
|
code: z.ZodLiteral<"organization/nomination_invalid">;
|
|
662
662
|
status: z.ZodLiteral<400>;
|
|
663
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
664
|
+
message: z.ZodString;
|
|
665
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
666
|
+
code: z.ZodLiteral<"organization/last_owner">;
|
|
667
|
+
status: z.ZodLiteral<409>;
|
|
668
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
669
|
+
message: z.ZodString;
|
|
670
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
671
|
+
code: z.ZodLiteral<"organization/already_member">;
|
|
672
|
+
status: z.ZodLiteral<409>;
|
|
673
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
674
|
+
message: z.ZodString;
|
|
675
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
676
|
+
code: z.ZodLiteral<"organization/seat_limit_reached">;
|
|
677
|
+
status: z.ZodLiteral<409>;
|
|
678
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
679
|
+
message: z.ZodString;
|
|
680
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
681
|
+
code: z.ZodLiteral<"organization/organization_suspended">;
|
|
682
|
+
status: z.ZodLiteral<403>;
|
|
663
683
|
}, z.core.$strip>, z.ZodObject<{
|
|
664
684
|
message: z.ZodString;
|
|
665
685
|
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
@@ -1660,6 +1680,34 @@ export declare const KitErrorPayload: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
1660
1680
|
detail: z.ZodOptional<z.ZodString>;
|
|
1661
1681
|
code: z.ZodLiteral<"organization/nomination_invalid">;
|
|
1662
1682
|
status: z.ZodLiteral<400>;
|
|
1683
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1684
|
+
message: z.ZodString;
|
|
1685
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
1686
|
+
action: z.ZodOptional<z.ZodString>;
|
|
1687
|
+
detail: z.ZodOptional<z.ZodString>;
|
|
1688
|
+
code: z.ZodLiteral<"organization/last_owner">;
|
|
1689
|
+
status: z.ZodLiteral<409>;
|
|
1690
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1691
|
+
message: z.ZodString;
|
|
1692
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
1693
|
+
action: z.ZodOptional<z.ZodString>;
|
|
1694
|
+
detail: z.ZodOptional<z.ZodString>;
|
|
1695
|
+
code: z.ZodLiteral<"organization/already_member">;
|
|
1696
|
+
status: z.ZodLiteral<409>;
|
|
1697
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1698
|
+
message: z.ZodString;
|
|
1699
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
1700
|
+
action: z.ZodOptional<z.ZodString>;
|
|
1701
|
+
detail: z.ZodOptional<z.ZodString>;
|
|
1702
|
+
code: z.ZodLiteral<"organization/seat_limit_reached">;
|
|
1703
|
+
status: z.ZodLiteral<409>;
|
|
1704
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1705
|
+
message: z.ZodString;
|
|
1706
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
1707
|
+
action: z.ZodOptional<z.ZodString>;
|
|
1708
|
+
detail: z.ZodOptional<z.ZodString>;
|
|
1709
|
+
code: z.ZodLiteral<"organization/organization_suspended">;
|
|
1710
|
+
status: z.ZodLiteral<403>;
|
|
1663
1711
|
}, z.core.$strip>, z.ZodObject<{
|
|
1664
1712
|
message: z.ZodString;
|
|
1665
1713
|
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
@@ -2467,6 +2515,26 @@ export declare const PublicErrorPayload: z.ZodUnion<readonly [z.ZodDiscriminated
|
|
|
2467
2515
|
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
2468
2516
|
code: z.ZodLiteral<"organization/nomination_invalid">;
|
|
2469
2517
|
status: z.ZodLiteral<400>;
|
|
2518
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
2519
|
+
message: z.ZodString;
|
|
2520
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
2521
|
+
code: z.ZodLiteral<"organization/last_owner">;
|
|
2522
|
+
status: z.ZodLiteral<409>;
|
|
2523
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
2524
|
+
message: z.ZodString;
|
|
2525
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
2526
|
+
code: z.ZodLiteral<"organization/already_member">;
|
|
2527
|
+
status: z.ZodLiteral<409>;
|
|
2528
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
2529
|
+
message: z.ZodString;
|
|
2530
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
2531
|
+
code: z.ZodLiteral<"organization/seat_limit_reached">;
|
|
2532
|
+
status: z.ZodLiteral<409>;
|
|
2533
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
2534
|
+
message: z.ZodString;
|
|
2535
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
2536
|
+
code: z.ZodLiteral<"organization/organization_suspended">;
|
|
2537
|
+
status: z.ZodLiteral<403>;
|
|
2470
2538
|
}, z.core.$strip>, z.ZodObject<{
|
|
2471
2539
|
message: z.ZodString;
|
|
2472
2540
|
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
@@ -3483,6 +3551,34 @@ export declare const ErrorPayload: z.ZodUnion<readonly [z.ZodDiscriminatedUnion<
|
|
|
3483
3551
|
detail: z.ZodOptional<z.ZodString>;
|
|
3484
3552
|
code: z.ZodLiteral<"organization/nomination_invalid">;
|
|
3485
3553
|
status: z.ZodLiteral<400>;
|
|
3554
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
3555
|
+
message: z.ZodString;
|
|
3556
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
3557
|
+
action: z.ZodOptional<z.ZodString>;
|
|
3558
|
+
detail: z.ZodOptional<z.ZodString>;
|
|
3559
|
+
code: z.ZodLiteral<"organization/last_owner">;
|
|
3560
|
+
status: z.ZodLiteral<409>;
|
|
3561
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
3562
|
+
message: z.ZodString;
|
|
3563
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
3564
|
+
action: z.ZodOptional<z.ZodString>;
|
|
3565
|
+
detail: z.ZodOptional<z.ZodString>;
|
|
3566
|
+
code: z.ZodLiteral<"organization/already_member">;
|
|
3567
|
+
status: z.ZodLiteral<409>;
|
|
3568
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
3569
|
+
message: z.ZodString;
|
|
3570
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
3571
|
+
action: z.ZodOptional<z.ZodString>;
|
|
3572
|
+
detail: z.ZodOptional<z.ZodString>;
|
|
3573
|
+
code: z.ZodLiteral<"organization/seat_limit_reached">;
|
|
3574
|
+
status: z.ZodLiteral<409>;
|
|
3575
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
3576
|
+
message: z.ZodString;
|
|
3577
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
3578
|
+
action: z.ZodOptional<z.ZodString>;
|
|
3579
|
+
detail: z.ZodOptional<z.ZodString>;
|
|
3580
|
+
code: z.ZodLiteral<"organization/organization_suspended">;
|
|
3581
|
+
status: z.ZodLiteral<403>;
|
|
3486
3582
|
}, z.core.$strip>, z.ZodObject<{
|
|
3487
3583
|
message: z.ZodString;
|
|
3488
3584
|
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payload.d.ts","sourceRoot":"","sources":["../../src/error/payload.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;GAQG;AAEH,uFAAuF;AACvF,eAAO,MAAM,eAAe;;;;iBAQqD,CAAC;AAClF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"payload.d.ts","sourceRoot":"","sources":["../../src/error/payload.ts"],"names":[],"mappings":"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"}
|
package/dist/error/payload.js
CHANGED
|
@@ -687,6 +687,26 @@ const OrganizationNominationInvalidPublic = z.object({
|
|
|
687
687
|
status: z.literal(400).describe("Bad Request — no offer this caller may accept."),
|
|
688
688
|
...publicFields
|
|
689
689
|
}).describe("No standing ownership offer this caller may accept (400).");
|
|
690
|
+
const OrganizationLastOwnerPublic = z.object({
|
|
691
|
+
code: z.literal("organization/last_owner").describe("An operator write would leave the account with nobody holding the role a transfer of ownership confers: removing or demoting its only owner. An account with no owner is the unadministrable state membership administration exists to get out of, so the way out is to force ownership onto somebody else first, which `params.ownershipRoute` names."),
|
|
692
|
+
status: z.literal(409).describe("Conflict — an account nobody owns is not a state to arrive at."),
|
|
693
|
+
...publicFields
|
|
694
|
+
}).describe("The last owner cannot be removed or demoted (409).");
|
|
695
|
+
const OrganizationAlreadyMemberPublic = z.object({
|
|
696
|
+
code: z.literal("organization/already_member").describe("An operator tried to add somebody who already holds a membership in the account. One membership per person per organization, so this is a refusal rather than a second row — and `params.role` names the role they already hold, because the next step is a role change rather than an add."),
|
|
697
|
+
status: z.literal(409).describe("Conflict."),
|
|
698
|
+
...publicFields
|
|
699
|
+
}).describe("That person is already a member (409).");
|
|
700
|
+
const OrganizationSeatLimitReachedPublic = z.object({
|
|
701
|
+
code: z.literal("organization/seat_limit_reached").describe("The account holds as many members as its seat limit allows, so another one would exceed it. `params.seatLimit` names the limit. An operator add may override it, and the override is recorded; an invitation accepted against a full account is refused until a seat frees."),
|
|
702
|
+
status: z.literal(409).describe("Conflict."),
|
|
703
|
+
...publicFields
|
|
704
|
+
}).describe("The account has no free seat (409).");
|
|
705
|
+
const OrganizationSuspendedPublic = z.object({
|
|
706
|
+
code: z.literal("organization/organization_suspended").describe("The organization this request acts in is suspended by an operator, so nobody can act in it until it is reinstated (#623). The caller's own account is not affected, and their other organizations are not either. `params.suspendedUntil`, when present, is when the suspension ends by itself, ISO-8601. Not `organization/forbidden`, because no role would let them, and not `organization/not_found`, because the caller is a proved member and already knows the organization exists."),
|
|
707
|
+
status: z.literal(403).describe("Forbidden — the organization may not be acted in, whoever asks."),
|
|
708
|
+
...publicFields
|
|
709
|
+
}).describe("The organization is suspended (403).");
|
|
690
710
|
const ArtifactsNotFoundPublic = z.object({
|
|
691
711
|
code: z.literal("artifacts/not_found").describe("No such repository, **or** one the caller holds no grant on and does not own. One code for both, byte for byte: a repository's name is in its clone URL, and a refusal that told the two apart would hand it over. Also what the credential route answers for a repository with remote access off. Which it was survives in `detail`, which the HTTP codec strips."),
|
|
692
712
|
status: z.literal(404).describe("Not Found."),
|
|
@@ -925,6 +945,10 @@ const KitPublicErrorPayload = z.discriminatedUnion("code", [
|
|
|
925
945
|
OrganizationLastAdministratorPublic,
|
|
926
946
|
OrganizationSlugTakenPublic,
|
|
927
947
|
OrganizationNominationInvalidPublic,
|
|
948
|
+
OrganizationLastOwnerPublic,
|
|
949
|
+
OrganizationAlreadyMemberPublic,
|
|
950
|
+
OrganizationSeatLimitReachedPublic,
|
|
951
|
+
OrganizationSuspendedPublic,
|
|
928
952
|
ArtifactsNotFoundPublic,
|
|
929
953
|
ArtifactsForbiddenPublic,
|
|
930
954
|
ArtifactsInvalidNamePublic,
|
|
@@ -1083,6 +1107,10 @@ const OrganizationInvitationInvalid = OrganizationInvitationInvalidPublic.extend
|
|
|
1083
1107
|
const OrganizationLastAdministrator = OrganizationLastAdministratorPublic.extend(internalFields).describe(OrganizationLastAdministratorPublic.description ?? "");
|
|
1084
1108
|
const OrganizationSlugTaken = OrganizationSlugTakenPublic.extend(internalFields).describe(OrganizationSlugTakenPublic.description ?? "");
|
|
1085
1109
|
const OrganizationNominationInvalid = OrganizationNominationInvalidPublic.extend(internalFields).describe(OrganizationNominationInvalidPublic.description ?? "");
|
|
1110
|
+
const OrganizationLastOwner = OrganizationLastOwnerPublic.extend(internalFields).describe(OrganizationLastOwnerPublic.description ?? "");
|
|
1111
|
+
const OrganizationAlreadyMember = OrganizationAlreadyMemberPublic.extend(internalFields).describe(OrganizationAlreadyMemberPublic.description ?? "");
|
|
1112
|
+
const OrganizationSeatLimitReached = OrganizationSeatLimitReachedPublic.extend(internalFields).describe(OrganizationSeatLimitReachedPublic.description ?? "");
|
|
1113
|
+
const OrganizationSuspended = OrganizationSuspendedPublic.extend(internalFields).describe(OrganizationSuspendedPublic.description ?? "");
|
|
1086
1114
|
const ArtifactsNotFound = ArtifactsNotFoundPublic.extend(internalFields).describe(ArtifactsNotFoundPublic.description ?? "");
|
|
1087
1115
|
const ArtifactsForbidden = ArtifactsForbiddenPublic.extend(internalFields).describe(ArtifactsForbiddenPublic.description ?? "");
|
|
1088
1116
|
const ArtifactsInvalidName = ArtifactsInvalidNamePublic.extend(internalFields).describe(ArtifactsInvalidNamePublic.description ?? "");
|
|
@@ -1226,6 +1254,10 @@ const KitErrorPayload = z.discriminatedUnion("code", [
|
|
|
1226
1254
|
OrganizationLastAdministrator,
|
|
1227
1255
|
OrganizationSlugTaken,
|
|
1228
1256
|
OrganizationNominationInvalid,
|
|
1257
|
+
OrganizationLastOwner,
|
|
1258
|
+
OrganizationAlreadyMember,
|
|
1259
|
+
OrganizationSeatLimitReached,
|
|
1260
|
+
OrganizationSuspended,
|
|
1229
1261
|
ArtifactsNotFound,
|
|
1230
1262
|
ArtifactsForbidden,
|
|
1231
1263
|
ArtifactsInvalidName,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
|
-
import type { ValidationTargets } from "hono";
|
|
3
|
+
import type { Context, MiddlewareHandler, ValidationTargets } from "hono";
|
|
4
4
|
import type { z } from "zod";
|
|
5
5
|
/**
|
|
6
6
|
* How a route declares what it accepts. Its sibling {@link ./verification.VerificationStrategy}
|
|
@@ -60,12 +60,21 @@ export interface RequestContract {
|
|
|
60
60
|
* app.post(path, requireAuth(), validate("param", BoardParam), validate("json", SubmitScoreBody), handler);
|
|
61
61
|
* ```
|
|
62
62
|
*/
|
|
63
|
-
export declare function validate<Target extends keyof ValidationTargets, Schema extends z.ZodType>(target: Target, schema: Schema):
|
|
63
|
+
export declare function validate<Target extends keyof ValidationTargets, Schema extends z.ZodType>(target: Target, schema: Schema, onInvalid?: InvalidRequestListener): MiddlewareHandler<import("hono").Env, string, {
|
|
64
64
|
in: (undefined extends (Schema extends import("zod/v3").ZodType<any, import("zod/v3").ZodTypeDef, any> ? import("zod/v3").input<Schema> : Schema extends z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>> ? z.core.input<Schema> : never) ? true : false) extends true ? { [K in Target]?: ([Schema extends import("zod/v3").ZodType<any, import("zod/v3").ZodTypeDef, any> ? import("zod/v3").input<Schema> : Schema extends z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>> ? z.core.input<Schema> : never] extends [ValidationTargets[K]] ? ValidationTargets[K] & (Schema extends import("zod/v3").ZodType<any, import("zod/v3").ZodTypeDef, any> ? import("zod/v3").input<Schema> : Schema extends z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>> ? z.core.input<Schema> : never) : import("hono/validator").InferInput<Schema extends import("zod/v3").ZodType<any, import("zod/v3").ZodTypeDef, any> ? import("zod/v3").input<Schema> : Schema extends z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>> ? z.core.input<Schema> : never, K, import("hono/types").ParsedFormValue>) | undefined; } : { [K in Target]: [Schema extends import("zod/v3").ZodType<any, import("zod/v3").ZodTypeDef, any> ? import("zod/v3").input<Schema> : Schema extends z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>> ? z.core.input<Schema> : never] extends [ValidationTargets[K]] ? ValidationTargets[K] & (Schema extends import("zod/v3").ZodType<any, import("zod/v3").ZodTypeDef, any> ? import("zod/v3").input<Schema> : Schema extends z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>> ? z.core.input<Schema> : never) : import("hono/validator").InferInput<Schema extends import("zod/v3").ZodType<any, import("zod/v3").ZodTypeDef, any> ? import("zod/v3").input<Schema> : Schema extends z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>> ? z.core.input<Schema> : never, K, import("hono/types").ParsedFormValue>; };
|
|
65
65
|
out: { [K in Target]: Schema extends import("zod/v3").ZodType<any, import("zod/v3").ZodTypeDef, any> ? import("zod/v3").output<Schema> : Schema extends z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>> ? z.infer<Schema> : never; };
|
|
66
66
|
}, never> & {
|
|
67
67
|
[REQUEST_CONTRACT]: RequestContract;
|
|
68
68
|
};
|
|
69
|
+
/**
|
|
70
|
+
* Told when {@link validate} refuses a request — a body that does not parse, or one that fails the schema —
|
|
71
|
+
* before the refusal is thrown. A route whose every attempt must be on the audit trail records the refusal
|
|
72
|
+
* here. It cannot turn the refusal into anything else: the error is thrown after it returns, and a listener
|
|
73
|
+
* that throws replaces it. `input` is what the request sent for the validated part, as it arrived; it is
|
|
74
|
+
* `undefined` when there was nothing to read, as for a body that is not JSON. Input validated by an earlier
|
|
75
|
+
* {@link validate} on the same route is readable with `c.req.valid(target)`.
|
|
76
|
+
*/
|
|
77
|
+
export type InvalidRequestListener = (c: Context, error: unknown, input: unknown) => Promise<void> | void;
|
|
69
78
|
/** The contract a {@link validate} middleware carries, or `undefined` for any other handler. */
|
|
70
79
|
export declare function requestContractOf(handler: unknown): RequestContract | undefined;
|
|
71
80
|
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/http/validation.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/http/validation.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC1E,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAG7B;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,CAAA;CAAE,CAAC;AAEhG;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAG/D;AAED,6GAA6G;AAC7G,eAAO,MAAM,gBAAgB,eAAsC,CAAC;AAEpE,4GAA4G;AAC5G,MAAM,WAAW,eAAe;IAC9B,kGAAkG;IAClG,QAAQ,CAAC,MAAM,EAAE,MAAM,iBAAiB,CAAC;IACzC,iFAAiF;IACjF,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC;CAC5B;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,MAAM,SAAS,MAAM,iBAAiB,EAAE,MAAM,SAAS,CAAC,CAAC,OAAO,EACvF,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,sBAAsB;;;;;EA0BnC;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAE1G,gGAAgG;AAChG,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,eAAe,GAAG,SAAS,CAG/E"}
|
package/dist/http/validation.js
CHANGED
|
@@ -23,13 +23,30 @@ const REQUEST_CONTRACT = Symbol.for("pithy.requestContract");
|
|
|
23
23
|
* app.post(path, requireAuth(), validate("param", BoardParam), validate("json", SubmitScoreBody), handler);
|
|
24
24
|
* ```
|
|
25
25
|
*/
|
|
26
|
-
function validate(target, schema) {
|
|
27
|
-
const middleware = zValidator(target, schema, validationHook);
|
|
26
|
+
function validate(target, schema, onInvalid) {
|
|
28
27
|
const contract = {
|
|
29
28
|
target,
|
|
30
29
|
schema
|
|
31
30
|
};
|
|
32
|
-
return Object.assign(
|
|
31
|
+
if (!onInvalid) return Object.assign(zValidator(target, schema, validationHook), { [REQUEST_CONTRACT]: contract });
|
|
32
|
+
const refusedInput = /* @__PURE__ */ new WeakMap();
|
|
33
|
+
const middleware = zValidator(target, schema, (outcome, c) => {
|
|
34
|
+
if (!outcome.success) refusedInput.set(c, outcome.data);
|
|
35
|
+
validationHook(outcome);
|
|
36
|
+
});
|
|
37
|
+
const listened = async (c, next) => {
|
|
38
|
+
let reached = false;
|
|
39
|
+
try {
|
|
40
|
+
return await middleware(c, async () => {
|
|
41
|
+
reached = true;
|
|
42
|
+
await next();
|
|
43
|
+
});
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (!reached) await onInvalid(c, error, refusedInput.get(c));
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
return Object.assign(listened, { [REQUEST_CONTRACT]: contract });
|
|
33
50
|
}
|
|
34
51
|
/** The contract a {@link validate} middleware carries, or `undefined` for any other handler. */
|
|
35
52
|
function requestContractOf(handler) {
|
package/dist/logger/record.d.ts
CHANGED
|
@@ -933,6 +933,34 @@ export declare const LogRecord: z.ZodObject<{
|
|
|
933
933
|
detail: z.ZodOptional<z.ZodString>;
|
|
934
934
|
code: z.ZodLiteral<"organization/nomination_invalid">;
|
|
935
935
|
status: z.ZodLiteral<400>;
|
|
936
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
937
|
+
message: z.ZodString;
|
|
938
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
939
|
+
action: z.ZodOptional<z.ZodString>;
|
|
940
|
+
detail: z.ZodOptional<z.ZodString>;
|
|
941
|
+
code: z.ZodLiteral<"organization/last_owner">;
|
|
942
|
+
status: z.ZodLiteral<409>;
|
|
943
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
944
|
+
message: z.ZodString;
|
|
945
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
946
|
+
action: z.ZodOptional<z.ZodString>;
|
|
947
|
+
detail: z.ZodOptional<z.ZodString>;
|
|
948
|
+
code: z.ZodLiteral<"organization/already_member">;
|
|
949
|
+
status: z.ZodLiteral<409>;
|
|
950
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
951
|
+
message: z.ZodString;
|
|
952
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
953
|
+
action: z.ZodOptional<z.ZodString>;
|
|
954
|
+
detail: z.ZodOptional<z.ZodString>;
|
|
955
|
+
code: z.ZodLiteral<"organization/seat_limit_reached">;
|
|
956
|
+
status: z.ZodLiteral<409>;
|
|
957
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
958
|
+
message: z.ZodString;
|
|
959
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
960
|
+
action: z.ZodOptional<z.ZodString>;
|
|
961
|
+
detail: z.ZodOptional<z.ZodString>;
|
|
962
|
+
code: z.ZodLiteral<"organization/organization_suspended">;
|
|
963
|
+
status: z.ZodLiteral<403>;
|
|
936
964
|
}, z.core.$strip>, z.ZodObject<{
|
|
937
965
|
message: z.ZodString;
|
|
938
966
|
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../../src/logger/record.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;GAKG;AAEH,4GAA4G;AAC5G,eAAO,MAAM,QAAQ;;;;;EAE4F,CAAC;AAClH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,QAAQ,CAAC,CAAC;AAEjD;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAgD,CAAC;AAEtG;;;;GAIG;AACH,eAAO,MAAM,SAAS,wCAEsF,CAAC;AAC7G,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,SAAS,CAAC,CAAC;AAEnD;;;;;GAKG;AACH,eAAO,MAAM,SAAS
|
|
1
|
+
{"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../../src/logger/record.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;GAKG;AAEH,4GAA4G;AAC5G,eAAO,MAAM,QAAQ;;;;;EAE4F,CAAC;AAClH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,QAAQ,CAAC,CAAC;AAEjD;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAgD,CAAC;AAEtG;;;;GAIG;AACH,eAAO,MAAM,SAAS,wCAEsF,CAAC;AAC7G,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,SAAS,CAAC,CAAC;AAEnD;;;;;GAKG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAW+E,CAAC;AACtG,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,SAAS,CAAC,CAAC;AAmBnD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAMzD"}
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
/** This package's npm name — the join key against a release feed. */
|
|
4
4
|
export declare const PACKAGE_NAME = "@pithy-sh/core";
|
|
5
5
|
/** This package's version, stamped from its own package.json at generation time. */
|
|
6
|
-
export declare const PACKAGE_VERSION = "0.
|
|
6
|
+
export declare const PACKAGE_VERSION = "0.13.0";
|
|
7
7
|
//# sourceMappingURL=version.generated.d.ts.map
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
/** This package's npm name — the join key against a release feed. */
|
|
5
5
|
const PACKAGE_NAME = "@pithy-sh/core";
|
|
6
6
|
/** This package's version, stamped from its own package.json at generation time. */
|
|
7
|
-
const PACKAGE_VERSION = "0.
|
|
7
|
+
const PACKAGE_VERSION = "0.13.0";
|
|
8
8
|
//#endregion
|
|
9
9
|
export { PACKAGE_NAME, PACKAGE_VERSION };
|
package/package.json
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import type { Capability } from "../capability/capability";
|
|
5
|
+
import { type AuditEmit, noopEmit } from "./recorder";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The audit recorder for code with no request — a Workflow step (#622).
|
|
9
|
+
*
|
|
10
|
+
* A route records through `c.var.emit`. A Workflow has no `c`: the runtime constructs it with `env` and nothing
|
|
11
|
+
* else. So it finds the composed capability that declares {@link Capability.auditRecorder} — `@pithy-sh/audit`
|
|
12
|
+
* — and records through that, built over this run's env. With none composed it is {@link noopEmit}, exactly as
|
|
13
|
+
* `c.var.emit` is in a Worker that composes no audit, so a step can always record without asking first.
|
|
14
|
+
*
|
|
15
|
+
* **Non-fatal, whatever the recorder does.** The contract says a recorder never throws; this holds a recorder
|
|
16
|
+
* to it rather than trusting it, because what a step records has already happened and a throw here would turn
|
|
17
|
+
* it into a retried step.
|
|
18
|
+
*/
|
|
19
|
+
export function composedAuditEmit(capabilities: readonly Capability[], env: Record<string, unknown>): AuditEmit {
|
|
20
|
+
const provider = capabilities.find((capability) => capability.auditRecorder !== undefined);
|
|
21
|
+
if (!provider?.auditRecorder) return noopEmit;
|
|
22
|
+
const record = provider.auditRecorder(env);
|
|
23
|
+
return async (event) => {
|
|
24
|
+
try {
|
|
25
|
+
await record(event);
|
|
26
|
+
} catch {
|
|
27
|
+
// The recorder broke its own contract. The event is lost, and the step it recorded stands.
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -633,6 +633,17 @@ export interface Capability<
|
|
|
633
633
|
* capability that declares one (e.g. `@pithy-sh/email`'s bounce/complaint handler).
|
|
634
634
|
*/
|
|
635
635
|
email?: CapabilityEmailHandler;
|
|
636
|
+
/**
|
|
637
|
+
* The audit recorder this capability provides **for code that has no request** — a Workflow step, which the
|
|
638
|
+
* runtime constructs with `env` and nothing else (#622). `@pithy-sh/audit` declares it; nothing else does.
|
|
639
|
+
*
|
|
640
|
+
* A route records through `c.var.emit`, which the same capability's middleware fills, and keeps doing that.
|
|
641
|
+
* This is the other door, for the purge that erases a deleted account: it runs in a Workflow and still owes
|
|
642
|
+
* the trail its event. `../audit/composed.ts` finds it among the composed capabilities, so a capability that
|
|
643
|
+
* audits from a step imports no audit package — principle 4, the same reason `c.var.emit` is a seam. Built
|
|
644
|
+
* from the env per call, like the request recorder is built per request. Non-fatal by the same contract.
|
|
645
|
+
*/
|
|
646
|
+
auditRecorder?: (env: Record<string, unknown>) => AuditEmit;
|
|
636
647
|
/**
|
|
637
648
|
* Named D1 databases this capability contributes tables to (database name → {@link DatabaseSpec}).
|
|
638
649
|
* `createBackend` merges every capability's slices per database (via `composeDatabases`) and serves
|
package/src/capability/purge.ts
CHANGED
|
@@ -23,6 +23,14 @@ import { ValidationError } from "../error/pithyError";
|
|
|
23
23
|
* a step is retried after a failure that may have come after its rows were already gone, and erasing
|
|
24
24
|
* nothing must then succeed.
|
|
25
25
|
*
|
|
26
|
+
* ## The same contribution answers an export (#622)
|
|
27
|
+
*
|
|
28
|
+
* An access request arrives before an erasure, not after. So a contribution may also say what this capability
|
|
29
|
+
* holds about the subject, on the read side: `export` answers one JSON object, and the control plane's export
|
|
30
|
+
* route assembles one document from every composed contribution, in the same order the purge runs them. A
|
|
31
|
+
* contribution with no `export` is listed as holding rows it did not say how to export, which is the honest
|
|
32
|
+
* answer and the one an operator can act on.
|
|
33
|
+
*
|
|
26
34
|
* ## An adopter declares one too
|
|
27
35
|
*
|
|
28
36
|
* On their own `app` capability, for their own tables, with the same types. The contract is exported for
|
|
@@ -31,12 +39,13 @@ import { ValidationError } from "../error/pithyError";
|
|
|
31
39
|
|
|
32
40
|
/**
|
|
33
41
|
* The kinds of subject a purge erases. Closed, so a typo is refused where it is written rather than becoming
|
|
34
|
-
* a contribution no purge ever asks for. `organization`
|
|
42
|
+
* a contribution no purge ever asks for. `organization` joined with organization state (#623): a capability
|
|
43
|
+
* holding rows scoped to a tenant declares one for it, and the organization purge runs it.
|
|
35
44
|
*/
|
|
36
45
|
export const PurgeSubject = z
|
|
37
|
-
.enum(["user"])
|
|
46
|
+
.enum(["user", "organization"])
|
|
38
47
|
.describe(
|
|
39
|
-
"The kind of subject a purge erases. `user` is one person's account in `@pithy-sh/auth`, identified by its user id.",
|
|
48
|
+
"The kind of subject a purge erases. `user` is one person's account in `@pithy-sh/auth`, identified by its user id. `organization` is one tenant in `@pithy-sh/organization`, identified by its organization id.",
|
|
40
49
|
);
|
|
41
50
|
export type PurgeSubject = z.output<typeof PurgeSubject>;
|
|
42
51
|
|
|
@@ -63,6 +72,13 @@ export interface PurgeContribution {
|
|
|
63
72
|
* and only this step.
|
|
64
73
|
*/
|
|
65
74
|
readonly erase: (context: PurgeContext) => Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* What this capability holds about the subject, as one JSON object — the read side of `erase` (#622). It
|
|
77
|
+
* answers an access request, so it carries the subject's own data and never a credential: no session token,
|
|
78
|
+
* no provider token, no push token. Optional: a contribution without one is listed in the export as rows it
|
|
79
|
+
* erases and did not say how to export. A throw costs this capability's entry, never the document.
|
|
80
|
+
*/
|
|
81
|
+
readonly export?: (context: PurgeContext) => Promise<Record<string, unknown>>;
|
|
66
82
|
}
|
|
67
83
|
|
|
68
84
|
/** One step of a purge: which capability it runs, and the contribution that capability declared. */
|
|
@@ -101,6 +117,12 @@ export function assertPurgeContributions(capability: string, contributions: read
|
|
|
101
117
|
action: "Say in one sentence what it erases. The purge records it with the step.",
|
|
102
118
|
});
|
|
103
119
|
}
|
|
120
|
+
if (contribution.export !== undefined && typeof contribution.export !== "function") {
|
|
121
|
+
throw new ValidationError({
|
|
122
|
+
message: `The ${capability} capability's ${subject.data} purge has an export that is not a function.`,
|
|
123
|
+
action: "Give it an `export` that answers what this capability holds about the subject, or leave it out.",
|
|
124
|
+
});
|
|
125
|
+
}
|
|
104
126
|
if (typeof contribution.erase !== "function") {
|
|
105
127
|
throw new ValidationError({
|
|
106
128
|
message: `The ${capability} capability's ${subject.data} purge has no erase function.`,
|
|
@@ -122,3 +144,44 @@ export function purgeSteps(
|
|
|
122
144
|
}
|
|
123
145
|
return steps;
|
|
124
146
|
}
|
|
147
|
+
|
|
148
|
+
/** What one step's export answered: its data, that it declared none, or that it could not be read. */
|
|
149
|
+
export type PurgeExportEntry =
|
|
150
|
+
| {
|
|
151
|
+
readonly capability: string;
|
|
152
|
+
readonly summary: string;
|
|
153
|
+
readonly state: "exported";
|
|
154
|
+
readonly data: Record<string, unknown>;
|
|
155
|
+
}
|
|
156
|
+
| { readonly capability: string; readonly summary: string; readonly state: "not_exported" }
|
|
157
|
+
| { readonly capability: string; readonly summary: string; readonly state: "unavailable" };
|
|
158
|
+
|
|
159
|
+
/** Whether an export answered what the contract promises: one plain object. */
|
|
160
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
161
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Read every step's export for one subject — the export document's body, in step order (#622).
|
|
166
|
+
*
|
|
167
|
+
* **Each read is guarded on its own and they run concurrently**, the shape the user pane's sub-reads have
|
|
168
|
+
* (#380): one capability whose table will not read costs its own entry as `unavailable`, and the rest of the
|
|
169
|
+
* document still answers the request. What the read threw is not carried — it names a query and a table, and
|
|
170
|
+
* the document crosses a trust boundary. An answer that is not one object is `unavailable` too, because the
|
|
171
|
+
* document promises an object per capability and a client validates it.
|
|
172
|
+
*/
|
|
173
|
+
export async function purgeExports(steps: readonly PurgeStep[], context: PurgeContext): Promise<PurgeExportEntry[]> {
|
|
174
|
+
return Promise.all(
|
|
175
|
+
steps.map(async ({ step, contribution }): Promise<PurgeExportEntry> => {
|
|
176
|
+
const base = { capability: step, summary: contribution.summary };
|
|
177
|
+
const read = contribution.export;
|
|
178
|
+
if (read === undefined) return { ...base, state: "not_exported" };
|
|
179
|
+
try {
|
|
180
|
+
const data = await read(context);
|
|
181
|
+
return isRecord(data) ? { ...base, state: "exported", data } : { ...base, state: "unavailable" };
|
|
182
|
+
} catch {
|
|
183
|
+
return { ...base, state: "unavailable" };
|
|
184
|
+
}
|
|
185
|
+
}),
|
|
186
|
+
);
|
|
187
|
+
}
|
|
@@ -903,6 +903,12 @@ export const COMMANDS = {
|
|
|
903
903
|
"accountReinstated",
|
|
904
904
|
"deletionScheduled",
|
|
905
905
|
"deletionComplete",
|
|
906
|
+
"organizationMemberAdded",
|
|
907
|
+
"organizationMemberRemoved",
|
|
908
|
+
"organizationOwnershipTransferred",
|
|
909
|
+
"organizationSuspended",
|
|
910
|
+
"organizationReinstated",
|
|
911
|
+
"organizationDeletionScheduled",
|
|
906
912
|
"operationalNotice",
|
|
907
913
|
"newsletter",
|
|
908
914
|
"leadCapture",
|
|
@@ -186,7 +186,21 @@ export function requireControlPlane(requirement: ControlPlaneRequirement): Middl
|
|
|
186
186
|
},
|
|
187
187
|
c.var.log,
|
|
188
188
|
);
|
|
189
|
-
|
|
189
|
+
// A handler can refuse a call the gate's requirement let through — an operator roster write that
|
|
190
|
+
// would move the owning role is refused `controlplane/insufficient_scope`, naming the ownership route,
|
|
191
|
+
// even on a connection granted `organization:ownership:transfer`, because a token exercises the one
|
|
192
|
+
// scope it was minted for — and it refuses it itself, past this gate. That refusal is still a blocked attempt, and the trail must
|
|
193
|
+
// say so: `call_allowed` alone would hide a credential probing for the grant it lacks.
|
|
194
|
+
//
|
|
195
|
+
// Both paths, as the organization routes' refusal recorder reads them: a throw that reaches here, and
|
|
196
|
+
// `c.error`, which is where Hono's `compose` leaves a downstream throw it rendered through `onError`.
|
|
197
|
+
try {
|
|
198
|
+
await next();
|
|
199
|
+
} catch (error) {
|
|
200
|
+
await auditHandlerDenial(c, requirement, context, error);
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
await auditHandlerDenial(c, requirement, context, c.error);
|
|
190
204
|
};
|
|
191
205
|
(guard as unknown as Record<symbol, unknown>)[CONTROL_PLANE_GUARD] = true;
|
|
192
206
|
return guard;
|
|
@@ -245,6 +259,51 @@ async function auditDenial(
|
|
|
245
259
|
);
|
|
246
260
|
}
|
|
247
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Record a scope refusal a handler raised after the gate let the call in.
|
|
264
|
+
*
|
|
265
|
+
* Only `controlplane/insufficient_scope`: every other refusal past the gate is the handler's own to
|
|
266
|
+
* record, and a 404 or a validation error is not a credential reaching beyond its grant. The same row as
|
|
267
|
+
* {@link auditDenial}, with two differences that follow from the call having been verified: the actor is
|
|
268
|
+
* the proven subject and its connection, and `refusedBy` says the gate passed it — `required` is the
|
|
269
|
+
* gate's requirement, which this call met; what it lacked is in the log, with the rest of `detail`.
|
|
270
|
+
*/
|
|
271
|
+
async function auditHandlerDenial(
|
|
272
|
+
c: Parameters<MiddlewareHandler<PithyHonoEnv>>[0],
|
|
273
|
+
requirement: ControlPlaneRequirement,
|
|
274
|
+
context: ControlPlaneContext,
|
|
275
|
+
cause: unknown,
|
|
276
|
+
): Promise<void> {
|
|
277
|
+
// Read by its code, not `instanceof`: a capability that throws it may hold its own copy of this module,
|
|
278
|
+
// and a refusal that slipped past a class check would be exactly the unaudited attempt this exists for.
|
|
279
|
+
const code = (cause as { payload?: { code?: unknown } } | null)?.payload?.code;
|
|
280
|
+
if (code !== "controlplane/insufficient_scope") return;
|
|
281
|
+
await safeEmit(
|
|
282
|
+
c.var.emit,
|
|
283
|
+
{
|
|
284
|
+
action: ControlPlaneAuditActions.callDenied,
|
|
285
|
+
outcome: "denied",
|
|
286
|
+
severity: "warning",
|
|
287
|
+
actorType: "control-plane",
|
|
288
|
+
actorId: context.subject,
|
|
289
|
+
resourceType: "controlplane_connection",
|
|
290
|
+
resourceId: context.connectionId,
|
|
291
|
+
requestId: c.req.header("cf-ray"),
|
|
292
|
+
ip: c.req.header("cf-connecting-ip"),
|
|
293
|
+
userAgent: c.req.header("user-agent"),
|
|
294
|
+
metadata: {
|
|
295
|
+
reason: code,
|
|
296
|
+
required: String(requirement),
|
|
297
|
+
refusedBy: "handler",
|
|
298
|
+
connectionId: context.connectionId,
|
|
299
|
+
method: c.req.method,
|
|
300
|
+
path: c.req.path,
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
c.var.log,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
248
307
|
/**
|
|
249
308
|
* The step that connects a management client, as this deployed Worker can name it: with its own environment when
|
|
250
309
|
* it knows it, and never with a Worker — it cannot know the `apps/` directory that provisioned it (#711).
|