@spacefast/common 0.0.26 → 0.2.1
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/agents/connect-targets.d.ts +6 -146
- package/dist/agents/connect-targets.js +63 -295
- package/dist/agents/private-key-oauth.d.ts +31 -1
- package/dist/agents/private-key-oauth.js +124 -33
- package/dist/agents/registry/core.d.ts +292 -0
- package/dist/agents/registry/core.js +1388 -0
- package/dist/agents/registry/deeplink-probes.d.ts +23 -0
- package/dist/agents/registry/deeplink-probes.js +18 -0
- package/dist/agents/registry/node.d.ts +56 -0
- package/dist/agents/registry/node.js +339 -0
- package/dist/brand-assets-build.js +1 -2
- package/dist/brand-assets.js +4 -7
- package/dist/brand.d.ts +17 -0
- package/dist/brand.js +24 -4
- package/dist/config/domains.d.ts +55 -28
- package/dist/config/domains.js +85 -41
- package/dist/config/index.js +1 -2
- package/dist/contracts/abuse.js +9 -24
- package/dist/contracts/access-profiles.d.ts +86 -0
- package/dist/contracts/access-profiles.js +89 -0
- package/dist/contracts/access.d.ts +21 -27
- package/dist/contracts/access.js +33 -64
- package/dist/contracts/activity.d.ts +18 -1
- package/dist/contracts/activity.js +70 -26
- package/dist/contracts/analytics.js +8 -10
- package/dist/contracts/api-keys.d.ts +77 -52
- package/dist/contracts/api-keys.js +44 -24
- package/dist/contracts/application-journal.d.ts +216 -0
- package/dist/contracts/application-journal.js +167 -0
- package/dist/contracts/auth.d.ts +90 -17
- package/dist/contracts/auth.js +86 -17
- package/dist/contracts/beta.d.ts +9 -4
- package/dist/contracts/beta.js +20 -16
- package/dist/contracts/billing.js +7 -11
- package/dist/contracts/bootstrap.d.ts +791 -0
- package/dist/contracts/bootstrap.js +51 -0
- package/dist/contracts/builds.d.ts +186 -7
- package/dist/contracts/builds.js +75 -21
- package/dist/contracts/channels.d.ts +43 -0
- package/dist/contracts/channels.js +25 -9
- package/dist/contracts/cli.d.ts +7 -0
- package/dist/contracts/cli.js +6 -0
- package/dist/contracts/collab-public.d.ts +1 -1
- package/dist/contracts/collab-public.js +5 -8
- package/dist/contracts/comments.d.ts +45 -35
- package/dist/contracts/comments.js +107 -77
- package/dist/contracts/commerce.d.ts +165 -0
- package/dist/contracts/commerce.js +146 -0
- package/dist/contracts/common.d.ts +3 -3
- package/dist/contracts/common.js +18 -29
- package/dist/contracts/content-contract-verification.d.ts +521 -0
- package/dist/contracts/content-contract-verification.js +320 -0
- package/dist/contracts/content-platform-fixture.d.ts +1278 -0
- package/dist/contracts/content-platform-fixture.js +44 -0
- package/dist/contracts/content-program.d.ts +994 -0
- package/dist/contracts/content-program.js +658 -0
- package/dist/contracts/content-sync.d.ts +290 -0
- package/dist/contracts/content-sync.js +167 -0
- package/dist/contracts/content.d.ts +432 -0
- package/dist/contracts/content.js +303 -0
- package/dist/contracts/continuation.d.ts +17 -1
- package/dist/contracts/continuation.js +23 -23
- package/dist/contracts/countries.js +3 -4
- package/dist/contracts/crons.d.ts +17 -0
- package/dist/contracts/crons.js +33 -15
- package/dist/contracts/dashboard-prefs.d.ts +56 -0
- package/dist/contracts/dashboard-prefs.js +51 -0
- package/dist/contracts/device-auth.d.ts +234 -37
- package/dist/contracts/device-auth.js +267 -53
- package/dist/contracts/docs.d.ts +6 -10
- package/dist/contracts/docs.js +5 -13
- package/dist/contracts/domains.d.ts +499 -102
- package/dist/contracts/domains.js +169 -58
- package/dist/contracts/email-preferences.d.ts +3 -3
- package/dist/contracts/email-preferences.js +6 -7
- package/dist/contracts/enums.d.ts +26 -26
- package/dist/contracts/enums.js +61 -54
- package/dist/contracts/error-code-meta.d.ts +269 -7
- package/dist/contracts/error-code-meta.js +80 -5
- package/dist/contracts/error-codes.d.ts +5 -5
- package/dist/contracts/error-codes.js +78 -16
- package/dist/contracts/events.d.ts +100 -20
- package/dist/contracts/events.js +107 -20
- package/dist/contracts/execution.d.ts +47 -66
- package/dist/contracts/execution.js +51 -64
- package/dist/contracts/feature-lifecycle.d.ts +70 -14
- package/dist/contracts/feature-lifecycle.js +86 -15
- package/dist/contracts/features.d.ts +100 -37
- package/dist/contracts/features.js +21 -27
- package/dist/contracts/fixtures/content-platform-v1.json +1108 -0
- package/dist/contracts/frame-session.d.ts +192 -0
- package/dist/contracts/frame-session.js +164 -0
- package/dist/contracts/functions.d.ts +218 -211
- package/dist/contracts/functions.js +273 -260
- package/dist/contracts/grant-copy.d.ts +43 -0
- package/dist/contracts/grant-copy.js +44 -0
- package/dist/contracts/grants.d.ts +64 -56
- package/dist/contracts/grants.js +124 -23
- package/dist/contracts/ids.d.ts +4 -1
- package/dist/contracts/ids.js +4 -1
- package/dist/contracts/internal.d.ts +5 -4
- package/dist/contracts/internal.js +12 -3
- package/dist/contracts/mcp.d.ts +17 -0
- package/dist/contracts/mcp.js +23 -0
- package/dist/contracts/me.d.ts +6 -1
- package/dist/contracts/me.js +12 -6
- package/dist/contracts/notifications.d.ts +27 -29
- package/dist/contracts/notifications.js +9 -4
- package/dist/contracts/oauth-resources.d.ts +112 -10
- package/dist/contracts/oauth-resources.js +116 -11
- package/dist/contracts/oauth-scope-actions.d.ts +90 -0
- package/dist/contracts/oauth-scope-actions.js +126 -0
- package/dist/contracts/operations.d.ts +27 -4
- package/dist/contracts/operations.js +11 -5
- package/dist/contracts/pages.d.ts +29 -5
- package/dist/contracts/pages.js +22 -5
- package/dist/contracts/partner-notifications.d.ts +3 -0
- package/dist/contracts/partner-notifications.js +65 -0
- package/dist/contracts/{platform.d.ts → partner.d.ts} +29 -42
- package/dist/contracts/{platform.js → partner.js} +27 -40
- package/dist/contracts/plan-policy.d.ts +1 -1
- package/dist/contracts/principal-assertion.d.ts +43 -0
- package/dist/contracts/principal-assertion.js +67 -0
- package/dist/contracts/principals.d.ts +73 -0
- package/dist/contracts/principals.js +59 -0
- package/dist/contracts/privacy.d.ts +11 -0
- package/dist/contracts/privacy.js +17 -0
- package/dist/contracts/problem-document.js +3 -4
- package/dist/contracts/publish-archive.d.ts +116 -18
- package/dist/contracts/publish-archive.js +6 -3
- package/dist/contracts/push-new.d.ts +4 -4
- package/dist/contracts/quotas.js +3 -3
- package/dist/contracts/realtime.d.ts +52 -0
- package/dist/contracts/realtime.js +54 -0
- package/dist/contracts/repository-connections.d.ts +1 -1
- package/dist/contracts/repository-connections.js +6 -2
- package/dist/contracts/resources.d.ts +105 -14
- package/dist/contracts/resources.js +145 -21
- package/dist/contracts/route-inventory.d.ts +358 -0
- package/dist/contracts/route-inventory.js +141 -0
- package/dist/contracts/runtime-api.d.ts +112 -35
- package/dist/contracts/runtime-api.js +86 -66
- package/dist/contracts/runtime-app.d.ts +11 -21
- package/dist/contracts/runtime-app.js +39 -24
- package/dist/contracts/runtime-components.d.ts +143 -0
- package/dist/contracts/runtime-components.js +199 -0
- package/dist/contracts/runtime-db.d.ts +45 -19
- package/dist/contracts/runtime-db.js +85 -20
- package/dist/contracts/runtime-purge.js +3 -5
- package/dist/contracts/runtime-services.d.ts +43 -75
- package/dist/contracts/runtime-services.js +29 -49
- package/dist/contracts/runtime-storage.d.ts +14 -14
- package/dist/contracts/runtime-storage.js +28 -13
- package/dist/contracts/sf-config-v1.d.ts +14 -18
- package/dist/contracts/slugs.js +13 -4
- package/dist/contracts/space-config.d.ts +235 -28
- package/dist/contracts/space-config.js +246 -91
- package/dist/contracts/spaces.d.ts +360 -255
- package/dist/contracts/spaces.js +190 -122
- package/dist/contracts/superadmin-activity.d.ts +3 -43
- package/dist/contracts/superadmin-activity.js +6 -30
- package/dist/contracts/superadmin-emails.d.ts +1 -0
- package/dist/contracts/superadmin-emails.js +17 -1
- package/dist/contracts/superadmin-runtime.d.ts +199 -0
- package/dist/contracts/superadmin-runtime.js +141 -0
- package/dist/contracts/superadmin-search.d.ts +22 -2
- package/dist/contracts/superadmin-search.js +14 -6
- package/dist/contracts/superadmin-spaces.d.ts +435 -186
- package/dist/contracts/superadmin-spaces.js +131 -25
- package/dist/contracts/superadmin-teams.d.ts +3 -0
- package/dist/contracts/superadmin-teams.js +1 -2
- package/dist/contracts/superadmin-tenants.d.ts +627 -6
- package/dist/contracts/superadmin-tenants.js +32 -7
- package/dist/contracts/superadmin.d.ts +98 -16
- package/dist/contracts/superadmin.js +63 -10
- package/dist/contracts/tags.d.ts +85 -58
- package/dist/contracts/tags.js +44 -34
- package/dist/contracts/teams.d.ts +72 -9
- package/dist/contracts/teams.js +84 -25
- package/dist/contracts/tenants.d.ts +135 -0
- package/dist/contracts/tenants.js +120 -0
- package/dist/contracts/test-triggers.d.ts +70 -0
- package/dist/contracts/test-triggers.js +116 -0
- package/dist/contracts/theme-json.js +3 -8
- package/dist/contracts/transfers.d.ts +1 -0
- package/dist/contracts/transfers.js +1 -0
- package/dist/contracts/usage.d.ts +209 -0
- package/dist/contracts/usage.js +124 -12
- package/dist/contracts/variables.d.ts +2 -2
- package/dist/contracts/variables.js +9 -13
- package/dist/contracts/webhooks.d.ts +13 -5
- package/dist/contracts/webhooks.js +3 -1
- package/dist/contracts/zero.d.ts +57 -64
- package/dist/contracts/zero.js +68 -66
- package/dist/dashboard-paths/index.d.ts +24 -21
- package/dist/dashboard-paths/index.js +35 -47
- package/dist/dashboard-paths/route-reservations.generated.d.ts +2 -0
- package/dist/dashboard-paths/route-reservations.generated.js +36 -0
- package/dist/docs/agent-arrival.d.ts +92 -0
- package/dist/docs/agent-arrival.js +227 -0
- package/dist/docs/agent-output-policy.d.ts +6 -0
- package/dist/docs/agent-output-policy.js +22 -0
- package/dist/docs/agent-prose.d.ts +11 -17
- package/dist/docs/agent-prose.js +62 -37
- package/dist/docs/agent-setup.d.ts +72 -41
- package/dist/docs/agent-setup.js +336 -436
- package/dist/docs/catalog.d.ts +147 -89
- package/dist/docs/catalog.js +157 -42
- package/dist/docs/error-docs.d.ts +2098 -1
- package/dist/docs/error-docs.js +281 -16
- package/dist/docs/index-build.d.ts +0 -1
- package/dist/docs/index-build.js +2 -3
- package/dist/docs/page-context-prompt.d.ts +77 -0
- package/dist/docs/page-context-prompt.js +131 -0
- package/dist/docs/reference-urls.d.ts +46 -0
- package/dist/docs/reference-urls.js +79 -0
- package/dist/docs/search.d.ts +6 -8
- package/dist/docs/search.js +10 -13
- package/dist/docs/skill-distribution.d.ts +4 -1
- package/dist/docs/skill-distribution.js +10 -5
- package/dist/docs/skills.d.ts +117 -117
- package/dist/docs/skills.js +120 -72
- package/dist/docs/start-prompts.d.ts +45 -0
- package/dist/docs/start-prompts.js +62 -0
- package/dist/domain-exploration.js +9 -13
- package/dist/og-template.d.ts +56 -0
- package/dist/og-template.js +56 -0
- package/dist/slug-policy/index.js +5 -11
- package/dist/utils/browser-credential.d.ts +13 -6
- package/dist/utils/browser-credential.js +14 -7
- package/dist/utils/build-settings.d.ts +16 -0
- package/dist/utils/build-settings.js +63 -17
- package/dist/utils/canonical-json.d.ts +3 -5
- package/dist/utils/canonical-json.js +3 -5
- package/dist/utils/cast-transport.d.ts +5 -17
- package/dist/utils/cast-transport.js +6 -19
- package/dist/utils/claim-token.js +1 -2
- package/dist/utils/comment-avatars.js +4 -6
- package/dist/utils/concurrency.js +2 -3
- package/dist/utils/content-type.d.ts +7 -6
- package/dist/utils/content-type.js +73 -51
- package/dist/utils/credential-policy.d.ts +50 -6
- package/dist/utils/credential-policy.js +123 -57
- package/dist/utils/egress-policy.fixtures.json +1 -1
- package/dist/utils/generate-space-name.d.ts +3 -8
- package/dist/utils/generate-space-name.js +3 -8
- package/dist/utils/grants.d.ts +0 -1
- package/dist/utils/grants.js +4 -7
- package/dist/utils/gravatar.d.ts +2 -4
- package/dist/utils/gravatar.js +6 -12
- package/dist/utils/id-hints.js +14 -23
- package/dist/utils/idempotency.d.ts +3 -3
- package/dist/utils/idempotency.js +6 -7
- package/dist/utils/local-space-state.d.ts +15 -15
- package/dist/utils/local-space-state.js +34 -23
- package/dist/utils/oauth-signed-query.d.ts +3 -5
- package/dist/utils/oauth-signed-query.js +6 -9
- package/dist/utils/one-shot-replay.d.ts +3 -5
- package/dist/utils/one-shot-replay.js +3 -5
- package/dist/utils/page-colors.d.ts +4 -10
- package/dist/utils/page-colors.js +11 -35
- package/dist/utils/page-fonts.js +6 -11
- package/dist/utils/page-preview.d.ts +0 -1
- package/dist/utils/page-preview.js +0 -1
- package/dist/utils/pages.d.ts +22 -11
- package/dist/utils/pages.js +70 -30
- package/dist/utils/privacy-regions.d.ts +19 -0
- package/dist/utils/privacy-regions.js +91 -0
- package/dist/utils/publish-form-data.d.ts +4 -5
- package/dist/utils/publish-form-data.js +2 -3
- package/dist/utils/publish-policy.d.ts +10 -23
- package/dist/utils/publish-policy.js +63 -116
- package/dist/utils/query-client.d.ts +2 -0
- package/dist/utils/query-client.js +10 -1
- package/dist/utils/runtime-paths.d.ts +4 -4
- package/dist/utils/runtime-paths.js +6 -8
- package/dist/utils/runtime-upload.d.ts +4 -0
- package/dist/utils/runtime-upload.js +5 -0
- package/dist/utils/secure-local-file.d.ts +3 -3
- package/dist/utils/secure-local-file.js +18 -24
- package/dist/utils/space-config.d.ts +11 -14
- package/dist/utils/space-config.js +10 -16
- package/dist/utils/static-runtime-policy.d.ts +5 -8
- package/dist/utils/static-runtime-policy.generated.d.ts +16 -0
- package/dist/utils/static-runtime-policy.generated.js +70 -0
- package/dist/utils/static-runtime-policy.js +17 -84
- package/dist/utils/storage-policy.js +1 -2
- package/dist/utils/upload-session.js +8 -14
- package/dist/utils/version-path.js +11 -19
- package/dist/utils/wpcom-auth-redirects.d.ts +5 -14
- package/dist/utils/wpcom-auth-redirects.js +1 -4
- package/dist/vocabulary.d.ts +7 -7
- package/dist/vocabulary.js +30 -16
- package/package.json +14 -2
- package/dist/agents/client-registry.d.ts +0 -22
- package/dist/agents/client-registry.js +0 -41
- package/dist/docs/agent-handoff-document.d.ts +0 -15
- package/dist/docs/agent-handoff-document.js +0 -180
- package/dist/docs/agent-solutions.d.ts +0 -110
- package/dist/docs/agent-solutions.js +0 -183
- package/dist/utils/query-keys.d.ts +0 -84
- package/dist/utils/query-keys.js +0 -108
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ApprovableScope } from "../contracts/access-profiles.js";
|
|
1
2
|
import { type ApiKeyPreset, type ApiCredentialPolicy, type StatticAction } from "../contracts/api-keys.js";
|
|
2
3
|
type RequestCondition = NonNullable<ApiCredentialPolicy["statements"][number]["conditions"]>;
|
|
3
4
|
export type PolicyCompileInput = {
|
|
@@ -8,21 +9,64 @@ export type PolicyAuthorizeInput = {
|
|
|
8
9
|
policy: ApiCredentialPolicy;
|
|
9
10
|
action: StatticAction;
|
|
10
11
|
/**
|
|
11
|
-
* Candidate
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* wins; otherwise an allow matching any candidate permits.
|
|
12
|
+
* Candidate URIs for one request target: a space is addressed both by its
|
|
13
|
+
* site-rooted URI and by a principal-scoped one. A deny on any candidate
|
|
14
|
+
* wins; an allow on any candidate permits.
|
|
15
15
|
*/
|
|
16
16
|
resource: string | readonly string[];
|
|
17
17
|
conditions?: RequestCondition;
|
|
18
18
|
};
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* What a publishing credential may do — the same set the `publish:write` OAuth
|
|
21
|
+
* scope grants, derived from the one scope⇄action table so an API-key preset
|
|
22
|
+
* and an OAuth client of the same tier can never drift apart.
|
|
23
|
+
*/
|
|
24
|
+
export declare const publishActions: ("teams:read" | "teams:create" | "spaces:read" | "spaces:write" | "spaces:rename" | "domains:read" | "spaces:publish" | "builds:trigger" | "*" | "spaces:create" | "spaces:delete" | "spaces:transfer" | "spaces:export" | "spaces:import" | "spaces:policy.write" | "versions:read" | "versions:promote" | "versions:delete" | "versions:download" | "domains:create" | "domains:verify" | "domains:assign" | "domains:bind" | "domains:delete" | "domains:dns.write" | "domains:dns.credentials.write" | "domains:purchase" | "webhooks:read" | "webhooks:write" | "builds:read" | "builds:create" | "variables:read" | "variables:write" | "tenants:read" | "tenants:write" | "principals:read" | "principals:write" | "infrastructure:read" | "teams:write" | "teams:members.write" | "billing:read" | "billing:write" | "tokens:read" | "tokens:create" | "tokens:revoke" | "runtime:manage")[];
|
|
25
|
+
/** The canonical policy resource for a team principal's subtree. */
|
|
26
|
+
export declare function teamResource(input: PolicyCompileInput): string;
|
|
20
27
|
export declare function compileApiKeyPreset(preset: ApiKeyPreset, input: PolicyCompileInput): ApiCredentialPolicy;
|
|
21
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Expand a named API-key preset into the finite permissions sent on the public
|
|
30
|
+
* wire contract. Deduplicated, because presets compose overlapping action
|
|
31
|
+
* groups and `allowPolicy` dedupes what it compiles — the wire list and the
|
|
32
|
+
* compiled statement must name the same actions.
|
|
33
|
+
*/
|
|
22
34
|
export declare function apiKeyPresetPermissions(preset: ApiKeyPreset): StatticAction[];
|
|
35
|
+
export declare function compileTenantRootedPolicy(input: {
|
|
36
|
+
tenantId: string;
|
|
37
|
+
preset: ApiKeyPreset;
|
|
38
|
+
}): ApiCredentialPolicy;
|
|
39
|
+
export declare function compilePartnerPrincipalPolicy(input: {
|
|
40
|
+
tenantId: string;
|
|
41
|
+
normalizedPrincipalId: string;
|
|
42
|
+
}): ApiCredentialPolicy;
|
|
23
43
|
export declare function compileTeamRolePolicy(input: PolicyCompileInput & {
|
|
24
44
|
role: string;
|
|
25
45
|
}): ApiCredentialPolicy;
|
|
46
|
+
/**
|
|
47
|
+
* The authority a membership-following key resolves to for one team: an action
|
|
48
|
+
* set bounded by the member's live role.
|
|
49
|
+
*
|
|
50
|
+
* Takes the action set rather than a preset name because a key's authority is
|
|
51
|
+
* not always its preset's CURRENT meaning. A key whose stored preset was
|
|
52
|
+
* redefined under it carries an explicit pin of the actions it was granted
|
|
53
|
+
* (`preset_actions_pin`), and that pin is what it keeps authorizing by.
|
|
54
|
+
*/
|
|
55
|
+
export declare function compileBoundedActionsPolicy(actions: readonly StatticAction[], input: PolicyCompileInput & {
|
|
56
|
+
role: string;
|
|
57
|
+
}): ApiCredentialPolicy;
|
|
58
|
+
/**
|
|
59
|
+
* Whether a person holding `role` in a team can currently exercise `scope`
|
|
60
|
+
* there. Account-plane scopes are admission the person always holds for
|
|
61
|
+
* themselves — no team role could bound them. Team-plane scopes need every
|
|
62
|
+
* action they grant inside the role's compiled policy.
|
|
63
|
+
*
|
|
64
|
+
* Informational, not a gate: approval no longer fails on role (authority is
|
|
65
|
+
* bounded live, per request, by `compileBoundedActionsPolicy`); the approval
|
|
66
|
+
* page uses this to tell the approver what a grant will actually do under
|
|
67
|
+
* their current role.
|
|
68
|
+
*/
|
|
69
|
+
export declare function scopeGrantableByRole(scope: ApprovableScope, role: string): boolean;
|
|
26
70
|
export declare function compileBoundedPresetPolicy(preset: ApiKeyPreset, input: PolicyCompileInput & {
|
|
27
71
|
role: string;
|
|
28
72
|
}): ApiCredentialPolicy;
|
|
@@ -1,16 +1,12 @@
|
|
|
1
|
+
import { SCOPE_PLANES } from "../contracts/access-profiles.js";
|
|
1
2
|
import { SPACEFAST_ACCESS_POLICY_VERSION, parseStatticResource, } from "../contracts/api-keys.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"versions:download",
|
|
10
|
-
"builds:read",
|
|
11
|
-
"builds:trigger",
|
|
12
|
-
"builds:create",
|
|
13
|
-
];
|
|
3
|
+
import { oauthScopeActions, oauthScopeActionsFor } from "../contracts/oauth-scope-actions.js";
|
|
4
|
+
/**
|
|
5
|
+
* What a publishing credential may do — the same set the `publish:write` OAuth
|
|
6
|
+
* scope grants, derived from the one scope⇄action table so an API-key preset
|
|
7
|
+
* and an OAuth client of the same tier can never drift apart.
|
|
8
|
+
*/
|
|
9
|
+
export const publishActions = oauthScopeActionsFor("publish:write");
|
|
14
10
|
const spaceAdminActions = [
|
|
15
11
|
...publishActions,
|
|
16
12
|
"spaces:rename",
|
|
@@ -19,22 +15,13 @@ const spaceAdminActions = [
|
|
|
19
15
|
"spaces:export",
|
|
20
16
|
"spaces:import",
|
|
21
17
|
"versions:delete",
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
// read access to the team resource itself.
|
|
18
|
+
// The CLI resolves the owning team before every publish, so admin-tier
|
|
19
|
+
// presets need read access to the team resource itself.
|
|
25
20
|
"teams:read",
|
|
26
21
|
];
|
|
27
|
-
const domainActions =
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"domains:verify",
|
|
31
|
-
"domains:assign",
|
|
32
|
-
"domains:bind",
|
|
33
|
-
"domains:delete",
|
|
34
|
-
"domains:dns.write",
|
|
35
|
-
"domains:purchase",
|
|
36
|
-
];
|
|
37
|
-
function teamResource(input) {
|
|
22
|
+
const domainActions = oauthScopeActionsFor("domains:write");
|
|
23
|
+
/** The canonical policy resource for a team principal's subtree. */
|
|
24
|
+
export function teamResource(input) {
|
|
38
25
|
return `stt://tenant/${input.tenantId}/principal/team/${input.teamId}/*`;
|
|
39
26
|
}
|
|
40
27
|
function spaceResource(input) {
|
|
@@ -43,21 +30,29 @@ function spaceResource(input) {
|
|
|
43
30
|
export function compileApiKeyPreset(preset, input) {
|
|
44
31
|
return allowPolicy(apiKeyPresetPermissions(preset), [teamResource(input)]);
|
|
45
32
|
}
|
|
46
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Expand a named API-key preset into the finite permissions sent on the public
|
|
35
|
+
* wire contract. Deduplicated, because presets compose overlapping action
|
|
36
|
+
* groups and `allowPolicy` dedupes what it compiles — the wire list and the
|
|
37
|
+
* compiled statement must name the same actions.
|
|
38
|
+
*/
|
|
47
39
|
export function apiKeyPresetPermissions(preset) {
|
|
40
|
+
return [...new Set(presetActions(preset))];
|
|
41
|
+
}
|
|
42
|
+
function presetActions(preset) {
|
|
48
43
|
switch (preset) {
|
|
49
44
|
case "ci_deploy":
|
|
50
45
|
case "space_publisher":
|
|
51
|
-
|
|
46
|
+
// `sf publish` resolves the owning team before it creates a Space, so a
|
|
47
|
+
// publishing key that cannot read its own team cannot publish a new one
|
|
48
|
+
// at all - the CLI's team pre-flight sees an empty list and stops before
|
|
49
|
+
// the API, which would have resolved the team from the key itself.
|
|
50
|
+
// `publishActions` stays the pure set of publish actions: claim
|
|
51
|
+
// continuation checks a team role against it.
|
|
52
|
+
return [...publishActions, "teams:read"];
|
|
52
53
|
case "space_admin":
|
|
53
|
-
return [...spaceAdminActions];
|
|
54
|
-
case "site_admin":
|
|
55
54
|
return [
|
|
56
|
-
"
|
|
57
|
-
"sites:create",
|
|
58
|
-
"sites:write",
|
|
59
|
-
"sites:delete",
|
|
60
|
-
"sites:policy.write",
|
|
55
|
+
"spaces:policy.write",
|
|
61
56
|
...spaceAdminActions,
|
|
62
57
|
...domainActions,
|
|
63
58
|
"webhooks:read",
|
|
@@ -69,9 +64,54 @@ export function apiKeyPresetPermissions(preset) {
|
|
|
69
64
|
return ["teams:read", "billing:read"];
|
|
70
65
|
case "team_admin":
|
|
71
66
|
return ["*"];
|
|
67
|
+
// The partner tier: everything a partner needs to run its own customers'
|
|
68
|
+
// spaces on our infrastructure, and nothing that mints credentials, edits
|
|
69
|
+
// team membership, or moves money.
|
|
70
|
+
case "partner_admin":
|
|
71
|
+
return [
|
|
72
|
+
"tenants:read",
|
|
73
|
+
"tenants:write",
|
|
74
|
+
"principals:read",
|
|
75
|
+
"principals:write",
|
|
76
|
+
"infrastructure:read",
|
|
77
|
+
"spaces:policy.write",
|
|
78
|
+
...spaceAdminActions,
|
|
79
|
+
...domainActions,
|
|
80
|
+
"webhooks:read",
|
|
81
|
+
"webhooks:write",
|
|
82
|
+
"teams:read",
|
|
83
|
+
"billing:read",
|
|
84
|
+
];
|
|
72
85
|
}
|
|
73
86
|
throw new Error("Unsupported API key preset.");
|
|
74
87
|
}
|
|
88
|
+
// A partner credential is rooted at the tenant, not at a team principal: it
|
|
89
|
+
// reaches every principal the tenant owns — teams and external customers alike —
|
|
90
|
+
// and stops at the tenant boundary. `compileApiKeyPreset` stays the team-rooted
|
|
91
|
+
// compiler; it needs a teamId this shape has no meaning for.
|
|
92
|
+
export function compileTenantRootedPolicy(input) {
|
|
93
|
+
return allowPolicy(apiKeyPresetPermissions(input.preset), [`stt://tenant/${input.tenantId}`]);
|
|
94
|
+
}
|
|
95
|
+
export function compilePartnerPrincipalPolicy(input) {
|
|
96
|
+
if (input.normalizedPrincipalId.includes("*") || input.normalizedPrincipalId.includes("/")) {
|
|
97
|
+
throw new Error("Partner principal id contains a reserved policy character.");
|
|
98
|
+
}
|
|
99
|
+
return allowPolicy([
|
|
100
|
+
"spaces:read",
|
|
101
|
+
"spaces:create",
|
|
102
|
+
"spaces:write",
|
|
103
|
+
"spaces:rename",
|
|
104
|
+
"spaces:publish",
|
|
105
|
+
"versions:read",
|
|
106
|
+
"versions:promote",
|
|
107
|
+
"versions:download",
|
|
108
|
+
"builds:read",
|
|
109
|
+
"builds:trigger",
|
|
110
|
+
"builds:create",
|
|
111
|
+
"variables:read",
|
|
112
|
+
"variables:write",
|
|
113
|
+
], [`stt://tenant/${input.tenantId}/principal/external/${input.normalizedPrincipalId}/*`]);
|
|
114
|
+
}
|
|
75
115
|
export function compileTeamRolePolicy(input) {
|
|
76
116
|
switch (input.role) {
|
|
77
117
|
case "owner":
|
|
@@ -79,7 +119,6 @@ export function compileTeamRolePolicy(input) {
|
|
|
79
119
|
return compileApiKeyPreset("team_admin", input);
|
|
80
120
|
case "member":
|
|
81
121
|
return allowPolicy([
|
|
82
|
-
"sites:read",
|
|
83
122
|
"spaces:read",
|
|
84
123
|
"spaces:create",
|
|
85
124
|
"spaces:write",
|
|
@@ -99,24 +138,56 @@ export function compileTeamRolePolicy(input) {
|
|
|
99
138
|
return denyAllPolicy();
|
|
100
139
|
}
|
|
101
140
|
}
|
|
102
|
-
|
|
103
|
-
|
|
141
|
+
/**
|
|
142
|
+
* The authority a membership-following key resolves to for one team: an action
|
|
143
|
+
* set bounded by the member's live role.
|
|
144
|
+
*
|
|
145
|
+
* Takes the action set rather than a preset name because a key's authority is
|
|
146
|
+
* not always its preset's CURRENT meaning. A key whose stored preset was
|
|
147
|
+
* redefined under it carries an explicit pin of the actions it was granted
|
|
148
|
+
* (`preset_actions_pin`), and that pin is what it keeps authorizing by.
|
|
149
|
+
*/
|
|
150
|
+
export function compileBoundedActionsPolicy(actions, input) {
|
|
104
151
|
const rolePolicy = compileTeamRolePolicy(input);
|
|
105
|
-
const presetActions = new Set(
|
|
106
|
-
.filter((statement) => statement.effect === "allow")
|
|
107
|
-
.flatMap((statement) => statement.actions));
|
|
152
|
+
const presetActions = new Set(actions);
|
|
108
153
|
const roleActions = new Set(rolePolicy.statements
|
|
109
154
|
.filter((statement) => statement.effect === "allow")
|
|
110
155
|
.flatMap((statement) => statement.actions));
|
|
111
|
-
const
|
|
156
|
+
const bounded = presetActions.has("*")
|
|
112
157
|
? [...roleActions]
|
|
113
158
|
: roleActions.has("*")
|
|
114
159
|
? [...presetActions]
|
|
115
160
|
: [...presetActions].filter((action) => roleActions.has(action));
|
|
116
|
-
if (
|
|
161
|
+
if (bounded.length === 0) {
|
|
117
162
|
return { version: SPACEFAST_ACCESS_POLICY_VERSION, statements: [] };
|
|
118
163
|
}
|
|
119
|
-
return allowPolicy(
|
|
164
|
+
return allowPolicy(bounded, [teamResource(input)]);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Whether a person holding `role` in a team can currently exercise `scope`
|
|
168
|
+
* there. Account-plane scopes are admission the person always holds for
|
|
169
|
+
* themselves — no team role could bound them. Team-plane scopes need every
|
|
170
|
+
* action they grant inside the role's compiled policy.
|
|
171
|
+
*
|
|
172
|
+
* Informational, not a gate: approval no longer fails on role (authority is
|
|
173
|
+
* bounded live, per request, by `compileBoundedActionsPolicy`); the approval
|
|
174
|
+
* page uses this to tell the approver what a grant will actually do under
|
|
175
|
+
* their current role.
|
|
176
|
+
*/
|
|
177
|
+
export function scopeGrantableByRole(scope, role) {
|
|
178
|
+
if (SCOPE_PLANES[scope] === "account") {
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
const roleActions = new Set(compileTeamRolePolicy({ tenantId: "-", teamId: "-", role })
|
|
182
|
+
.statements.filter((statement) => statement.effect === "allow")
|
|
183
|
+
.flatMap((statement) => statement.actions));
|
|
184
|
+
if (roleActions.has("*")) {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
return oauthScopeActions([scope]).every((action) => roleActions.has(action));
|
|
188
|
+
}
|
|
189
|
+
export function compileBoundedPresetPolicy(preset, input) {
|
|
190
|
+
return compileBoundedActionsPolicy(apiKeyPresetPermissions(preset), input);
|
|
120
191
|
}
|
|
121
192
|
export function compileSpaceContinuationPolicy(input) {
|
|
122
193
|
return allowPolicy(publishActions.filter((action) => action !== "spaces:create"), [spaceResource(input)]);
|
|
@@ -189,9 +260,8 @@ export function grantedPrincipalsFromPolicy(policy, input) {
|
|
|
189
260
|
if (!parsed || (parsed.tenantId !== input.tenantId && parsed.tenantId !== "*")) {
|
|
190
261
|
continue;
|
|
191
262
|
}
|
|
192
|
-
//
|
|
193
|
-
// `stt://tenant/{t}`
|
|
194
|
-
// they reach every principal in the tenant.
|
|
263
|
+
// A tenant-rooted grant is scope-wide: `stt://tenant/*` and a concrete
|
|
264
|
+
// `stt://tenant/{t}` both reach every principal in the tenant.
|
|
195
265
|
if (parsed.kind === "tenant") {
|
|
196
266
|
all = true;
|
|
197
267
|
}
|
|
@@ -226,9 +296,8 @@ export function grantedPrincipalsFromPolicy(policy, input) {
|
|
|
226
296
|
],
|
|
227
297
|
};
|
|
228
298
|
}
|
|
229
|
-
// Whether the policy's allow statements reach a resource subtree at all
|
|
230
|
-
//
|
|
231
|
-
// This is a visibility/scoping check (404 vs 403), never an action grant.
|
|
299
|
+
// Whether the policy's allow statements reach a resource subtree at all. This
|
|
300
|
+
// is a visibility/scoping check (404 vs 403), never an action grant.
|
|
232
301
|
export function policyReaches(policy, resourceBase) {
|
|
233
302
|
return policy.statements.some((statement) => statement.effect === "allow" &&
|
|
234
303
|
statement.resources.some((resource) => resourceMatches(resource, resourceBase) ||
|
|
@@ -259,10 +328,8 @@ function resourceMatches(pattern, resource) {
|
|
|
259
328
|
if (pattern.endsWith("/*")) {
|
|
260
329
|
return resource === pattern.slice(0, -2) || resource.startsWith(pattern.slice(0, -1));
|
|
261
330
|
}
|
|
262
|
-
// A tenant-rooted resource
|
|
263
|
-
//
|
|
264
|
-
// their subtree): it covers every resource inside that tenant. This is the
|
|
265
|
-
// tenant-scoped (partner/agency) credential shape.
|
|
331
|
+
// A tenant-rooted resource covers every resource inside that tenant. This is
|
|
332
|
+
// the tenant-scoped (partner/agency) credential shape.
|
|
266
333
|
if (parseStatticResource(pattern)?.kind === "tenant") {
|
|
267
334
|
return resource.startsWith(`${pattern}/`);
|
|
268
335
|
}
|
|
@@ -276,9 +343,8 @@ function conditionsMatch(statementConditions, contextConditions, effect, mode) {
|
|
|
276
343
|
// SAFETY: Object.entries obtains key from the named RequestCondition object immediately above.
|
|
277
344
|
const resolved = contextConditions?.[key];
|
|
278
345
|
if (resolved === undefined && mode === "request") {
|
|
279
|
-
// Fail closed
|
|
280
|
-
//
|
|
281
|
-
// callers cannot dodge a deny by stripping request context.
|
|
346
|
+
// Fail closed: a caller cannot dodge a deny by stripping the request
|
|
347
|
+
// context its condition reads.
|
|
282
348
|
return effect === "deny";
|
|
283
349
|
}
|
|
284
350
|
return resolved === value;
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
{ "host": "sub.localhost", "allowed": false },
|
|
46
46
|
{ "host": "169.254.169.254", "allowed": false },
|
|
47
47
|
{ "host": "view.fast", "allowed": false },
|
|
48
|
-
{ "host": "
|
|
48
|
+
{ "host": "site-123.view.fast", "allowed": false },
|
|
49
49
|
{ "host": "example.com", "allowed": true }
|
|
50
50
|
]
|
|
51
51
|
}
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* motion, and craft vocabulary. The result should suit any site instead of
|
|
6
|
-
* reading like a CMS inside joke, while staying compact enough for a hostname.
|
|
7
|
-
*
|
|
8
|
-
* The generator is intentionally dependency-free so it can be shared by the
|
|
9
|
-
* control-plane, dashboard, CLI, and future publishing surfaces.
|
|
2
|
+
* Short, readable space titles. Keep the pools site-neutral and short enough
|
|
3
|
+
* for a hostname. Dependency-free so the control plane, dashboard and CLI can
|
|
4
|
+
* all share it.
|
|
10
5
|
*/
|
|
11
6
|
export declare const SPACE_TITLE_COMBINATION_COUNT: number;
|
|
12
7
|
export declare function generateSpaceTitle(random?: () => number): string;
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* motion, and craft vocabulary. The result should suit any site instead of
|
|
6
|
-
* reading like a CMS inside joke, while staying compact enough for a hostname.
|
|
7
|
-
*
|
|
8
|
-
* The generator is intentionally dependency-free so it can be shared by the
|
|
9
|
-
* control-plane, dashboard, CLI, and future publishing surfaces.
|
|
2
|
+
* Short, readable space titles. Keep the pools site-neutral and short enough
|
|
3
|
+
* for a hostname. Dependency-free so the control plane, dashboard and CLI can
|
|
4
|
+
* all share it.
|
|
10
5
|
*/
|
|
11
6
|
const ADJECTIVES = [
|
|
12
7
|
"Atomic",
|
package/dist/utils/grants.d.ts
CHANGED
|
@@ -44,7 +44,6 @@ export type GrantEvaluation = {
|
|
|
44
44
|
matches: GrantMatch[];
|
|
45
45
|
};
|
|
46
46
|
export declare function grantResourcePatternMatches(pattern: string, resource: string, resourceKind?: "route" | "artifact"): boolean;
|
|
47
|
-
/** Does a Grant's resource set reach this path? Include, minus exclude. */
|
|
48
47
|
export declare function grantResourceSetMatches(resources: Grant["resources"], resource: string, resourceKind?: "route" | "artifact"): boolean;
|
|
49
48
|
export type GrantEvaluationExplanation = {
|
|
50
49
|
grantId: string;
|
package/dist/utils/grants.js
CHANGED
|
@@ -73,12 +73,10 @@ export function grantResourcePatternMatches(pattern, resource, resourceKind = "r
|
|
|
73
73
|
return matches(0, 0);
|
|
74
74
|
}
|
|
75
75
|
/**
|
|
76
|
-
* Exclude
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* Include never gets the same courtesy — granting the stripped route would hand
|
|
81
|
-
* out access nobody wrote down. The serving engine compiles the identical pair
|
|
76
|
+
* Exclude reads wider than include, deliberately: the route lane collapses a
|
|
77
|
+
* terminal `index.html`, so an exclude naming an index artifact must also close
|
|
78
|
+
* the route it serves. Include must not, or it would grant a route nobody wrote
|
|
79
|
+
* down. The serving engine compiles the same pair
|
|
82
80
|
* (access-rules.php _stattic_grant_exclude_pattern_variants).
|
|
83
81
|
*/
|
|
84
82
|
function grantExcludePatternMatches(pattern, resource, resourceKind) {
|
|
@@ -90,7 +88,6 @@ function grantExcludePatternMatches(pattern, resource, resourceKind) {
|
|
|
90
88
|
const stripped = canonical.slice(0, -"/index.html".length);
|
|
91
89
|
return grantResourcePatternMatches(stripped === "" ? "/" : stripped, resource, resourceKind);
|
|
92
90
|
}
|
|
93
|
-
/** Does a Grant's resource set reach this path? Include, minus exclude. */
|
|
94
91
|
export function grantResourceSetMatches(resources, resource, resourceKind = "route") {
|
|
95
92
|
return (resources.include.some((pattern) => grantResourcePatternMatches(pattern, resource, resourceKind)) &&
|
|
96
93
|
!resources.exclude.some((pattern) => grantExcludePatternMatches(pattern, resource, resourceKind)));
|
package/dist/utils/gravatar.d.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
/** Gravatar's canonical address form: trimmed and lowercased. */
|
|
2
1
|
export declare function normalizeGravatarEmail(email: string | null | undefined): string | null;
|
|
3
|
-
/** The canonical avatar URL for an already-computed SHA-256 hash. */
|
|
4
2
|
export declare function gravatarUrlFromHash(hashHex: string): string;
|
|
5
3
|
/**
|
|
6
|
-
* SHA-256 hex of a normalized email
|
|
7
|
-
* a browser
|
|
4
|
+
* Gravatar's identifier, the SHA-256 hex of a normalized email. WebCrypto lets
|
|
5
|
+
* a browser derive it locally and never send the raw address anywhere.
|
|
8
6
|
*/
|
|
9
7
|
export declare function gravatarEmailHash(email: string | null | undefined): Promise<string | null>;
|
package/dist/utils/gravatar.js
CHANGED
|
@@ -1,26 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The one Gravatar URL shape Spacefast emits
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* `d=404` is deliberate: a person without a Gravatar gets no image at all, and
|
|
8
|
-
* the consumer renders its own initials/palette fallback instead of a stock
|
|
9
|
-
* silhouette. `s=160` covers the largest avatar we render, retina included.
|
|
2
|
+
* The one Gravatar URL shape Spacefast emits, so Cast can validate a guest
|
|
3
|
+
* avatar against a single pattern. `d=404` returns no image for a person
|
|
4
|
+
* without a Gravatar, leaving the consumer's own fallback to render; `s=160`
|
|
5
|
+
* covers the largest avatar we render, retina included.
|
|
10
6
|
*/
|
|
11
7
|
const GRAVATAR_AVATAR_QUERY = "d=404&r=g&s=160";
|
|
12
|
-
/** Gravatar's canonical address form: trimmed and lowercased. */
|
|
13
8
|
export function normalizeGravatarEmail(email) {
|
|
14
9
|
const normalized = email?.trim().toLowerCase();
|
|
15
10
|
return normalized ? normalized : null;
|
|
16
11
|
}
|
|
17
|
-
/** The canonical avatar URL for an already-computed SHA-256 hash. */
|
|
18
12
|
export function gravatarUrlFromHash(hashHex) {
|
|
19
13
|
return `https://gravatar.com/avatar/${hashHex}?${GRAVATAR_AVATAR_QUERY}`;
|
|
20
14
|
}
|
|
21
15
|
/**
|
|
22
|
-
* SHA-256 hex of a normalized email
|
|
23
|
-
* a browser
|
|
16
|
+
* Gravatar's identifier, the SHA-256 hex of a normalized email. WebCrypto lets
|
|
17
|
+
* a browser derive it locally and never send the raw address anywhere.
|
|
24
18
|
*/
|
|
25
19
|
export async function gravatarEmailHash(email) {
|
|
26
20
|
const normalized = normalizeGravatarEmail(email);
|
package/dist/utils/id-hints.js
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// to the caller principal — this module never touches the DB or auth.
|
|
7
|
-
// How many caller-scoped rows a hint builder will scan to rank candidates. Above
|
|
8
|
-
// this the collection is treated as too large to rank for free, so the builder
|
|
9
|
-
// returns only the recovery hint (no candidate ids).
|
|
1
|
+
// One closest-candidate ranking for every resource's 404 hint builder (spaces,
|
|
2
|
+
// versions, teams, domains). Callers scope the rows to the caller principal;
|
|
3
|
+
// this module never touches the DB or auth.
|
|
4
|
+
// Above this many rows the collection is too large to rank for free, and the
|
|
5
|
+
// builder returns the recovery hint without candidate ids.
|
|
10
6
|
export const HINT_SCAN_CAP = 200;
|
|
11
7
|
export const HINT_MAX_CANDIDATES = 5;
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// from turning attacker-controlled refs or stored labels into quadratic work.
|
|
8
|
+
// Every hint key (ids, slugs, hostnames, version refs) fits here. Rejecting
|
|
9
|
+
// larger values keeps attacker-controlled refs out of quadratic work.
|
|
15
10
|
export const HINT_MAX_KEY_LENGTH = 256;
|
|
16
11
|
function boundedLevenshtein(a, b, maxDistance) {
|
|
17
12
|
if (Math.abs(a.length - b.length) > maxDistance)
|
|
@@ -48,9 +43,8 @@ export function levenshtein(a, b) {
|
|
|
48
43
|
}
|
|
49
44
|
return boundedLevenshtein(a, b, Math.max(a.length, b.length));
|
|
50
45
|
}
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
// containment = 1; otherwise a typo within a length-bounded edit distance.
|
|
46
|
+
// Lower is closer, +Infinity when not close at all: exact = 0, substring
|
|
47
|
+
// containment = 1, otherwise 1 + a length-bounded edit distance.
|
|
54
48
|
export function refCloseness(needle, candidate) {
|
|
55
49
|
if (!candidate)
|
|
56
50
|
return Number.POSITIVE_INFINITY;
|
|
@@ -61,22 +55,19 @@ export function refCloseness(needle, candidate) {
|
|
|
61
55
|
return 0;
|
|
62
56
|
if (candidate.includes(needle) || needle.includes(candidate))
|
|
63
57
|
return 1;
|
|
64
|
-
//
|
|
65
|
-
// string so short refs don't match everything.
|
|
58
|
+
// Bound the distance by the shorter string so short refs don't match everything.
|
|
66
59
|
const tolerance = Math.max(2, Math.floor(Math.min(needle.length, candidate.length) / 3));
|
|
67
60
|
const distance = boundedLevenshtein(needle, candidate, tolerance);
|
|
68
61
|
return distance <= tolerance ? 1 + distance : Number.POSITIVE_INFINITY;
|
|
69
62
|
}
|
|
70
|
-
// Rank `rows` by
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
// (e.g. recency); keys are matched case-insensitively.
|
|
63
|
+
// Rank `rows` by their closest key to `ref`, at most `max` rows. Rows with no
|
|
64
|
+
// close key are dropped. Ties break by the optional comparator; keys match
|
|
65
|
+
// case-insensitively.
|
|
74
66
|
export function rankCandidates(ref, rows, keysOf, options) {
|
|
75
67
|
if (rows.length > HINT_SCAN_CAP || ref.length > HINT_MAX_KEY_LENGTH)
|
|
76
68
|
return [];
|
|
77
69
|
const needle = ref.toLowerCase();
|
|
78
|
-
//
|
|
79
|
-
// that normalized representation under the same work ceiling too.
|
|
70
|
+
// Lowercasing can expand a string (U+0130), so re-check the ceiling.
|
|
80
71
|
if (needle.length > HINT_MAX_KEY_LENGTH)
|
|
81
72
|
return [];
|
|
82
73
|
const max = options?.max ?? HINT_MAX_CANDIDATES;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Stable, secret-safe key for one
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Stable, secret-safe key for one logical mutation attempt. The same publish
|
|
3
|
+
* from the CLI and from MCP must land on one key, or the API stops
|
|
4
|
+
* deduplicating the retry.
|
|
5
5
|
*/
|
|
6
6
|
export declare function stableIdempotencyKey(prefix: string, parts: unknown): string;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
/**
|
|
3
|
-
* Key-sorted JSON that treats an absent key and an `undefined` value
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* to hash alike. Merging the two spellings would change every minted key.
|
|
3
|
+
* Key-sorted JSON that treats an absent key and an `undefined` value alike: a
|
|
4
|
+
* key the CLI omits and one MCP sets to `undefined` are the same mutation.
|
|
5
|
+
* Deliberately not `canonicalJson`; merging the two would change every key.
|
|
7
6
|
*/
|
|
8
7
|
function keyJson(value) {
|
|
9
8
|
if (Array.isArray(value)) {
|
|
@@ -19,9 +18,9 @@ function keyJson(value) {
|
|
|
19
18
|
return JSON.stringify(value ?? null);
|
|
20
19
|
}
|
|
21
20
|
/**
|
|
22
|
-
* Stable, secret-safe key for one
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* Stable, secret-safe key for one logical mutation attempt. The same publish
|
|
22
|
+
* from the CLI and from MCP must land on one key, or the API stops
|
|
23
|
+
* deduplicating the retry.
|
|
25
24
|
*/
|
|
26
25
|
export function stableIdempotencyKey(prefix, parts) {
|
|
27
26
|
return `${prefix}-${createHash("sha256").update(keyJson(parts)).digest("hex")}`;
|