@spacefast/common 0.0.24 → 0.0.26
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/brand-assets/LICENSE-Haskoy.txt +93 -0
- package/brand-assets/haskoy-latin-variable.woff2 +0 -0
- package/brand-assets/spacefast-favicon.svg +4 -4
- package/brand-assets/spacefast-sf-full-bleed.svg +3 -3
- package/brand-assets/spacefast-wordmark.svg +2 -4
- package/dist/agents/connect-targets.js +1 -1
- package/dist/agents/private-key-oauth.d.ts +0 -1
- package/dist/agents/private-key-oauth.js +46 -63
- package/dist/brand-assets-build.js +4 -6
- package/dist/brand-assets.d.ts +5 -15
- package/dist/brand-assets.js +15 -14
- package/dist/config/domains.d.ts +47 -7
- package/dist/config/domains.js +65 -10
- package/dist/contracts/access.d.ts +3 -3
- package/dist/contracts/activity.d.ts +1 -1
- package/dist/contracts/activity.js +5 -3
- package/dist/contracts/api-keys.js +2 -2
- package/dist/contracts/billing.d.ts +2 -1
- package/dist/contracts/billing.js +30 -0
- package/dist/contracts/builds.d.ts +34 -22
- package/dist/contracts/builds.js +18 -6
- package/dist/contracts/channels.js +1 -1
- package/dist/contracts/comments.d.ts +1 -1
- package/dist/contracts/common.d.ts +6 -6
- package/dist/contracts/common.js +5 -5
- package/dist/contracts/crons.d.ts +35 -0
- package/dist/contracts/crons.js +42 -0
- package/dist/contracts/device-auth.js +1 -1
- package/dist/contracts/docs.d.ts +5 -5
- package/dist/contracts/docs.js +2 -2
- package/dist/contracts/domains.d.ts +74 -63
- package/dist/contracts/domains.js +29 -2
- package/dist/contracts/enums.d.ts +3 -2
- package/dist/contracts/enums.js +10 -10
- package/dist/contracts/error-code-meta.d.ts +14 -0
- package/dist/contracts/error-code-meta.js +17 -0
- package/dist/contracts/error-codes.d.ts +1 -1
- package/dist/contracts/error-codes.js +4 -1
- package/dist/contracts/execution.d.ts +15 -12
- package/dist/contracts/execution.js +15 -12
- package/dist/contracts/feature-lifecycle.d.ts +4 -14
- package/dist/contracts/feature-lifecycle.js +0 -11
- package/dist/contracts/features.d.ts +33 -47
- package/dist/contracts/features.js +27 -20
- package/dist/contracts/functions.d.ts +169 -2
- package/dist/contracts/functions.js +160 -4
- package/dist/contracts/git.d.ts +27 -33
- package/dist/contracts/git.js +10 -11
- package/dist/contracts/grants.d.ts +2 -2
- package/dist/contracts/ids.js +2 -2
- package/dist/contracts/internal.d.ts +5 -16
- package/dist/contracts/internal.js +2 -12
- package/dist/contracts/mcp.d.ts +11 -0
- package/dist/contracts/mcp.js +5 -0
- package/dist/contracts/me.js +1 -1
- package/dist/contracts/operations.d.ts +4 -4
- package/dist/contracts/operations.js +2 -2
- package/dist/contracts/pages.d.ts +6 -6
- package/dist/contracts/plan-policy.d.ts +1 -1
- package/dist/contracts/plan-policy.js +1 -1
- package/dist/contracts/platform.d.ts +4 -4
- package/dist/contracts/publish-archive.d.ts +170 -160
- package/dist/contracts/publish-archive.js +8 -1
- package/dist/contracts/push-new.d.ts +4 -4
- package/dist/contracts/quotas.js +2 -2
- package/dist/contracts/repository-connections.d.ts +43 -30
- package/dist/contracts/repository-connections.js +11 -3
- package/dist/contracts/resources.d.ts +8 -8
- package/dist/contracts/resources.js +6 -6
- package/dist/contracts/runtime-api.d.ts +103 -56
- package/dist/contracts/runtime-api.js +67 -26
- package/dist/contracts/runtime-app.d.ts +2 -2
- package/dist/contracts/runtime-db.d.ts +6 -6
- package/dist/contracts/runtime-db.js +2 -2
- package/dist/contracts/runtime-purge.d.ts +8 -0
- package/dist/contracts/runtime-purge.js +7 -0
- package/dist/contracts/runtime-storage.d.ts +3 -2
- package/dist/contracts/runtime-storage.js +2 -1
- package/dist/contracts/sf-config-v1.d.ts +48 -10
- package/dist/contracts/space-config.d.ts +60 -3
- package/dist/contracts/space-config.js +213 -18
- package/dist/contracts/spaces.d.ts +55 -74
- package/dist/contracts/spaces.js +11 -14
- package/dist/contracts/superadmin-spaces.d.ts +9 -9
- package/dist/contracts/superadmin-teams.d.ts +2 -2
- package/dist/contracts/superadmin-tenants.d.ts +0 -6
- package/dist/contracts/superadmin-tenants.js +1 -7
- package/dist/contracts/superadmin.d.ts +135 -22
- package/dist/contracts/superadmin.js +43 -12
- package/dist/contracts/tags.d.ts +21 -21
- package/dist/contracts/tags.js +3 -3
- package/dist/contracts/teams.d.ts +14 -17
- package/dist/contracts/teams.js +6 -6
- package/dist/contracts/transfers.js +2 -2
- package/dist/contracts/variables.d.ts +4 -4
- package/dist/contracts/variables.js +3 -3
- package/dist/contracts/webhooks.d.ts +4 -4
- package/dist/contracts/zero.d.ts +15 -17
- package/dist/contracts/zero.js +12 -12
- package/dist/docs/agent-prose.d.ts +1 -1
- package/dist/docs/agent-prose.js +11 -14
- package/dist/docs/agent-setup.js +5 -2
- package/dist/docs/catalog.d.ts +7 -7
- package/dist/docs/catalog.js +7 -7
- package/dist/docs/error-docs.js +145 -133
- package/dist/docs/skills.d.ts +12 -12
- package/dist/docs/skills.js +4 -4
- package/dist/test-helpers/fetch-stub.js +1 -0
- package/dist/utils/build-settings.d.ts +5 -1
- package/dist/utils/build-settings.js +82 -10
- package/dist/utils/canonical-json.js +1 -0
- package/dist/utils/concurrency.js +1 -0
- package/dist/utils/credential-policy.js +4 -3
- package/dist/utils/dns-instructions.js +9 -9
- package/dist/utils/error-code.js +1 -0
- package/dist/utils/grant-decision.fixtures.json +2 -2
- package/dist/utils/grants.js +3 -1
- package/dist/utils/local-space-state.d.ts +8 -11
- package/dist/utils/local-space-state.js +13 -24
- package/dist/utils/one-shot-replay.d.ts +4 -1
- package/dist/utils/page-colors.d.ts +14 -12
- package/dist/utils/page-colors.js +15 -13
- package/dist/utils/page-fonts.d.ts +1 -1
- package/dist/utils/page-fonts.js +11 -18
- package/dist/utils/pages.d.ts +20 -1
- package/dist/utils/pages.js +22 -9
- package/dist/utils/publish-policy.d.ts +7 -0
- package/dist/utils/publish-policy.fixtures.json +11 -5
- package/dist/utils/publish-policy.js +9 -1
- package/dist/utils/query-keys.d.ts +4 -4
- package/dist/utils/query-keys.js +2 -2
- package/dist/utils/runtime-upload.d.ts +5 -0
- package/dist/utils/runtime-upload.js +7 -0
- package/dist/utils/secure-local-file.js +5 -16
- package/dist/utils/space-config.d.ts +1 -1
- package/dist/utils/space-config.js +3 -0
- package/dist/utils/static-runtime-policy.d.ts +9 -3
- package/dist/utils/static-runtime-policy.fixtures.json +38 -41
- package/dist/utils/static-runtime-policy.js +22 -56
- package/dist/utils/upload-session.d.ts +4 -1
- package/dist/utils/upload-session.js +33 -5
- package/dist/utils/version-path.js +1 -1
- package/dist/vocabulary.d.ts +8 -8
- package/dist/vocabulary.js +31 -20
- package/package.json +2 -17
package/dist/docs/skills.d.ts
CHANGED
|
@@ -23,13 +23,13 @@ export interface SpacefastSkillDefinition {
|
|
|
23
23
|
export declare const SPACEFAST_SKILLS: readonly [{
|
|
24
24
|
readonly name: "create-and-publish-site";
|
|
25
25
|
readonly title: "Create and publish a polished website";
|
|
26
|
-
readonly description: "Turn a brief into a responsive
|
|
26
|
+
readonly description: "Turn a brief into a responsive website, review the result, publish it to Spacefast, and verify the live version.";
|
|
27
27
|
readonly whenToUse: "Use when the user wants an agent to design, build, improve, or launch a new website rather than only upload an existing build.";
|
|
28
28
|
readonly keywords: readonly ["create", "website", "design", "build", "responsive", "launch", "publish"];
|
|
29
29
|
readonly runtimes: readonly ["local", "hosted"];
|
|
30
30
|
readonly tools: readonly ["publish", "execute"];
|
|
31
31
|
readonly docs: readonly ["quickstart", "publishing"];
|
|
32
|
-
readonly instructions: readonly ["Clarify the audience, goal, required content, brand constraints, and desired call to action. If the visual direction is open, propose two or three distinct directions and choose one with the user.", "Inspect the existing workspace before editing. Reuse its framework, package manager, components, assets, and build conventions when they are sound.", "Build a focused, accessible
|
|
32
|
+
readonly instructions: readonly ["Clarify the audience, goal, required content, brand constraints, and desired call to action. If the visual direction is open, propose two or three distinct directions and choose one with the user.", "Inspect the existing workspace before editing. Reuse its framework, package manager, components, assets, and build conventions when they are sound.", "Build a focused, accessible experience with a clear hierarchy, real content, responsive layouts, useful interaction states, and no placeholder copy. Keep the main path simple and put secondary detail behind progressive disclosure.", "Run the real build and inspect the result at mobile and desktop widths. Fix overflow, illegible contrast, broken links, missing assets, keyboard traps, and avoidable layout shift.", "Review the workspace diff and summarize material design or content judgments before publishing. Never include API keys, claim credentials, upload credentials, or local auth state in the artifact.", "Publish the built output with `publish`. Present Open as the reusable access link and Claim as the one-time ownership link. Treat live and immutable-version URLs as receipt metadata, not extra actions."];
|
|
33
33
|
readonly verification: readonly ["Call `status` after publishing and confirm the intended version is live; use `diagnose` if serving is stalled or failed.", "Open the live URL and verify the primary page, navigation, assets, responsive layout, and main call to action before claiming success."];
|
|
34
34
|
readonly runtimeNotes: {
|
|
35
35
|
readonly local: "On-Device can work directly inside the configured local workspace root and publish a local build path.";
|
|
@@ -44,13 +44,13 @@ export declare const SPACEFAST_SKILLS: readonly [{
|
|
|
44
44
|
}, {
|
|
45
45
|
readonly name: "publish-and-verify";
|
|
46
46
|
readonly title: "Publish and verify";
|
|
47
|
-
readonly description: "Publish an existing
|
|
47
|
+
readonly description: "Publish an existing build, confirm the new version is actually live, and return the useful URLs without leaking credentials.";
|
|
48
48
|
readonly whenToUse: "Use when the artifact already exists and the user wants it published or updated.";
|
|
49
49
|
readonly keywords: readonly ["publish", "deploy", "live", "verify", "version", "claim"];
|
|
50
50
|
readonly runtimes: readonly ["local", "hosted"];
|
|
51
51
|
readonly tools: readonly ["publish", "status", "diagnose"];
|
|
52
52
|
readonly docs: readonly ["publishing", "workflow/publish-receipt", "workflow/secret-handling"];
|
|
53
|
-
readonly instructions: readonly ["Identify the actual publish root or build output. Run the real build first when the source tree is not itself the
|
|
53
|
+
readonly instructions: readonly ["Identify the actual publish root or build output. Run the real build first when the source tree is not itself the artifact to publish.", "Publish with `publish`. For an anonymous On-Device publish, keep the claim credential in local custody and return only the claim URL and expiry; never expose or ask the user to paste the raw token.", "Report the bare Live URL, private Access URL when present, immutable Version URL when present, anonymous Claim URL and expiry when present, plus actionable diagnostics."];
|
|
54
54
|
readonly verification: readonly ["Call `status` and confirm the intended version is live. Use `diagnose` for a failed or stalled publish.", "Open the live URL and verify representative content and assets rather than treating a successful upload or health check as serving proof."];
|
|
55
55
|
readonly runtimeNotes: {
|
|
56
56
|
readonly local: "Publish a bounded local path; locally custodied claim credentials are reused automatically.";
|
|
@@ -196,13 +196,13 @@ export declare const SPACEFAST_SKILLS: readonly [{
|
|
|
196
196
|
export declare const SPACEFAST_PROMPT_SKILLS: ({
|
|
197
197
|
readonly name: "create-and-publish-site";
|
|
198
198
|
readonly title: "Create and publish a polished website";
|
|
199
|
-
readonly description: "Turn a brief into a responsive
|
|
199
|
+
readonly description: "Turn a brief into a responsive website, review the result, publish it to Spacefast, and verify the live version.";
|
|
200
200
|
readonly whenToUse: "Use when the user wants an agent to design, build, improve, or launch a new website rather than only upload an existing build.";
|
|
201
201
|
readonly keywords: readonly ["create", "website", "design", "build", "responsive", "launch", "publish"];
|
|
202
202
|
readonly runtimes: readonly ["local", "hosted"];
|
|
203
203
|
readonly tools: readonly ["publish", "execute"];
|
|
204
204
|
readonly docs: readonly ["quickstart", "publishing"];
|
|
205
|
-
readonly instructions: readonly ["Clarify the audience, goal, required content, brand constraints, and desired call to action. If the visual direction is open, propose two or three distinct directions and choose one with the user.", "Inspect the existing workspace before editing. Reuse its framework, package manager, components, assets, and build conventions when they are sound.", "Build a focused, accessible
|
|
205
|
+
readonly instructions: readonly ["Clarify the audience, goal, required content, brand constraints, and desired call to action. If the visual direction is open, propose two or three distinct directions and choose one with the user.", "Inspect the existing workspace before editing. Reuse its framework, package manager, components, assets, and build conventions when they are sound.", "Build a focused, accessible experience with a clear hierarchy, real content, responsive layouts, useful interaction states, and no placeholder copy. Keep the main path simple and put secondary detail behind progressive disclosure.", "Run the real build and inspect the result at mobile and desktop widths. Fix overflow, illegible contrast, broken links, missing assets, keyboard traps, and avoidable layout shift.", "Review the workspace diff and summarize material design or content judgments before publishing. Never include API keys, claim credentials, upload credentials, or local auth state in the artifact.", "Publish the built output with `publish`. Present Open as the reusable access link and Claim as the one-time ownership link. Treat live and immutable-version URLs as receipt metadata, not extra actions."];
|
|
206
206
|
readonly verification: readonly ["Call `status` after publishing and confirm the intended version is live; use `diagnose` if serving is stalled or failed.", "Open the live URL and verify the primary page, navigation, assets, responsive layout, and main call to action before claiming success."];
|
|
207
207
|
readonly runtimeNotes: {
|
|
208
208
|
readonly local: "On-Device can work directly inside the configured local workspace root and publish a local build path.";
|
|
@@ -217,13 +217,13 @@ export declare const SPACEFAST_PROMPT_SKILLS: ({
|
|
|
217
217
|
} | {
|
|
218
218
|
readonly name: "publish-and-verify";
|
|
219
219
|
readonly title: "Publish and verify";
|
|
220
|
-
readonly description: "Publish an existing
|
|
220
|
+
readonly description: "Publish an existing build, confirm the new version is actually live, and return the useful URLs without leaking credentials.";
|
|
221
221
|
readonly whenToUse: "Use when the artifact already exists and the user wants it published or updated.";
|
|
222
222
|
readonly keywords: readonly ["publish", "deploy", "live", "verify", "version", "claim"];
|
|
223
223
|
readonly runtimes: readonly ["local", "hosted"];
|
|
224
224
|
readonly tools: readonly ["publish", "status", "diagnose"];
|
|
225
225
|
readonly docs: readonly ["publishing", "workflow/publish-receipt", "workflow/secret-handling"];
|
|
226
|
-
readonly instructions: readonly ["Identify the actual publish root or build output. Run the real build first when the source tree is not itself the
|
|
226
|
+
readonly instructions: readonly ["Identify the actual publish root or build output. Run the real build first when the source tree is not itself the artifact to publish.", "Publish with `publish`. For an anonymous On-Device publish, keep the claim credential in local custody and return only the claim URL and expiry; never expose or ask the user to paste the raw token.", "Report the bare Live URL, private Access URL when present, immutable Version URL when present, anonymous Claim URL and expiry when present, plus actionable diagnostics."];
|
|
227
227
|
readonly verification: readonly ["Call `status` and confirm the intended version is live. Use `diagnose` for a failed or stalled publish.", "Open the live URL and verify representative content and assets rather than treating a successful upload or health check as serving proof."];
|
|
228
228
|
readonly runtimeNotes: {
|
|
229
229
|
readonly local: "Publish a bounded local path; locally custodied claim credentials are reused automatically.";
|
|
@@ -369,13 +369,13 @@ export declare const SPACEFAST_PROMPT_SKILLS: ({
|
|
|
369
369
|
export declare const SPACEFAST_RECIPE_SKILLS: (({
|
|
370
370
|
readonly name: "create-and-publish-site";
|
|
371
371
|
readonly title: "Create and publish a polished website";
|
|
372
|
-
readonly description: "Turn a brief into a responsive
|
|
372
|
+
readonly description: "Turn a brief into a responsive website, review the result, publish it to Spacefast, and verify the live version.";
|
|
373
373
|
readonly whenToUse: "Use when the user wants an agent to design, build, improve, or launch a new website rather than only upload an existing build.";
|
|
374
374
|
readonly keywords: readonly ["create", "website", "design", "build", "responsive", "launch", "publish"];
|
|
375
375
|
readonly runtimes: readonly ["local", "hosted"];
|
|
376
376
|
readonly tools: readonly ["publish", "execute"];
|
|
377
377
|
readonly docs: readonly ["quickstart", "publishing"];
|
|
378
|
-
readonly instructions: readonly ["Clarify the audience, goal, required content, brand constraints, and desired call to action. If the visual direction is open, propose two or three distinct directions and choose one with the user.", "Inspect the existing workspace before editing. Reuse its framework, package manager, components, assets, and build conventions when they are sound.", "Build a focused, accessible
|
|
378
|
+
readonly instructions: readonly ["Clarify the audience, goal, required content, brand constraints, and desired call to action. If the visual direction is open, propose two or three distinct directions and choose one with the user.", "Inspect the existing workspace before editing. Reuse its framework, package manager, components, assets, and build conventions when they are sound.", "Build a focused, accessible experience with a clear hierarchy, real content, responsive layouts, useful interaction states, and no placeholder copy. Keep the main path simple and put secondary detail behind progressive disclosure.", "Run the real build and inspect the result at mobile and desktop widths. Fix overflow, illegible contrast, broken links, missing assets, keyboard traps, and avoidable layout shift.", "Review the workspace diff and summarize material design or content judgments before publishing. Never include API keys, claim credentials, upload credentials, or local auth state in the artifact.", "Publish the built output with `publish`. Present Open as the reusable access link and Claim as the one-time ownership link. Treat live and immutable-version URLs as receipt metadata, not extra actions."];
|
|
379
379
|
readonly verification: readonly ["Call `status` after publishing and confirm the intended version is live; use `diagnose` if serving is stalled or failed.", "Open the live URL and verify the primary page, navigation, assets, responsive layout, and main call to action before claiming success."];
|
|
380
380
|
readonly runtimeNotes: {
|
|
381
381
|
readonly local: "On-Device can work directly inside the configured local workspace root and publish a local build path.";
|
|
@@ -390,13 +390,13 @@ export declare const SPACEFAST_RECIPE_SKILLS: (({
|
|
|
390
390
|
} | {
|
|
391
391
|
readonly name: "publish-and-verify";
|
|
392
392
|
readonly title: "Publish and verify";
|
|
393
|
-
readonly description: "Publish an existing
|
|
393
|
+
readonly description: "Publish an existing build, confirm the new version is actually live, and return the useful URLs without leaking credentials.";
|
|
394
394
|
readonly whenToUse: "Use when the artifact already exists and the user wants it published or updated.";
|
|
395
395
|
readonly keywords: readonly ["publish", "deploy", "live", "verify", "version", "claim"];
|
|
396
396
|
readonly runtimes: readonly ["local", "hosted"];
|
|
397
397
|
readonly tools: readonly ["publish", "status", "diagnose"];
|
|
398
398
|
readonly docs: readonly ["publishing", "workflow/publish-receipt", "workflow/secret-handling"];
|
|
399
|
-
readonly instructions: readonly ["Identify the actual publish root or build output. Run the real build first when the source tree is not itself the
|
|
399
|
+
readonly instructions: readonly ["Identify the actual publish root or build output. Run the real build first when the source tree is not itself the artifact to publish.", "Publish with `publish`. For an anonymous On-Device publish, keep the claim credential in local custody and return only the claim URL and expiry; never expose or ask the user to paste the raw token.", "Report the bare Live URL, private Access URL when present, immutable Version URL when present, anonymous Claim URL and expiry when present, plus actionable diagnostics."];
|
|
400
400
|
readonly verification: readonly ["Call `status` and confirm the intended version is live. Use `diagnose` for a failed or stalled publish.", "Open the live URL and verify representative content and assets rather than treating a successful upload or health check as serving proof."];
|
|
401
401
|
readonly runtimeNotes: {
|
|
402
402
|
readonly local: "Publish a bounded local path; locally custodied claim credentials are reused automatically.";
|
package/dist/docs/skills.js
CHANGED
|
@@ -5,7 +5,7 @@ export const SPACEFAST_SKILLS = [
|
|
|
5
5
|
{
|
|
6
6
|
name: "create-and-publish-site",
|
|
7
7
|
title: "Create and publish a polished website",
|
|
8
|
-
description: "Turn a brief into a responsive
|
|
8
|
+
description: "Turn a brief into a responsive website, review the result, publish it to Spacefast, and verify the live version.",
|
|
9
9
|
whenToUse: "Use when the user wants an agent to design, build, improve, or launch a new website rather than only upload an existing build.",
|
|
10
10
|
keywords: ["create", "website", "design", "build", "responsive", "launch", "publish"],
|
|
11
11
|
runtimes: BOTH_RUNTIMES,
|
|
@@ -14,7 +14,7 @@ export const SPACEFAST_SKILLS = [
|
|
|
14
14
|
instructions: [
|
|
15
15
|
"Clarify the audience, goal, required content, brand constraints, and desired call to action. If the visual direction is open, propose two or three distinct directions and choose one with the user.",
|
|
16
16
|
"Inspect the existing workspace before editing. Reuse its framework, package manager, components, assets, and build conventions when they are sound.",
|
|
17
|
-
"Build a focused, accessible
|
|
17
|
+
"Build a focused, accessible experience with a clear hierarchy, real content, responsive layouts, useful interaction states, and no placeholder copy. Keep the main path simple and put secondary detail behind progressive disclosure.",
|
|
18
18
|
"Run the real build and inspect the result at mobile and desktop widths. Fix overflow, illegible contrast, broken links, missing assets, keyboard traps, and avoidable layout shift.",
|
|
19
19
|
"Review the workspace diff and summarize material design or content judgments before publishing. Never include API keys, claim credentials, upload credentials, or local auth state in the artifact.",
|
|
20
20
|
"Publish the built output with `publish`. Present Open as the reusable access link and Claim as the one-time ownership link. Treat live and immutable-version URLs as receipt metadata, not extra actions.",
|
|
@@ -37,14 +37,14 @@ export const SPACEFAST_SKILLS = [
|
|
|
37
37
|
{
|
|
38
38
|
name: "publish-and-verify",
|
|
39
39
|
title: "Publish and verify",
|
|
40
|
-
description: "Publish an existing
|
|
40
|
+
description: "Publish an existing build, confirm the new version is actually live, and return the useful URLs without leaking credentials.",
|
|
41
41
|
whenToUse: "Use when the artifact already exists and the user wants it published or updated.",
|
|
42
42
|
keywords: ["publish", "deploy", "live", "verify", "version", "claim"],
|
|
43
43
|
runtimes: BOTH_RUNTIMES,
|
|
44
44
|
tools: ["publish", "status", "diagnose"],
|
|
45
45
|
docs: ["publishing", "workflow/publish-receipt", "workflow/secret-handling"],
|
|
46
46
|
instructions: [
|
|
47
|
-
"Identify the actual publish root or build output. Run the real build first when the source tree is not itself the
|
|
47
|
+
"Identify the actual publish root or build output. Run the real build first when the source tree is not itself the artifact to publish.",
|
|
48
48
|
"Publish with `publish`. For an anonymous On-Device publish, keep the claim credential in local custody and return only the claim URL and expiry; never expose or ask the user to paste the raw token.",
|
|
49
49
|
"Report the bare Live URL, private Access URL when present, immutable Version URL when present, anonymous Claim URL and expiry when present, plus actionable diagnostics.",
|
|
50
50
|
],
|
|
@@ -2,6 +2,7 @@ import { errorDocsUrl, errorTitle, PROBLEM_CONTENT_TYPE } from "../contracts/err
|
|
|
2
2
|
/** The original fetch implementation captured before a test installs a stub. */
|
|
3
3
|
export const realFetch = globalThis.fetch;
|
|
4
4
|
export function stubFetch(handler) {
|
|
5
|
+
// SAFETY: The wrapper accepts fetch's parameter types and always resolves to the handler's Response.
|
|
5
6
|
globalThis.fetch = (async (input, init) => handler(input instanceof Request && init === undefined ? input : new Request(input, init)));
|
|
6
7
|
}
|
|
7
8
|
export function restoreFetch() {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SpaceCronConfig } from "../contracts/space-config.js";
|
|
1
2
|
export type BuildSettingsInput = {
|
|
2
3
|
fileNames: Iterable<string>;
|
|
3
4
|
packageJson?: string | null | undefined;
|
|
@@ -24,7 +25,7 @@ export type BuildAnalyzerCandidateRankInput = {
|
|
|
24
25
|
rootDirectory: string | null;
|
|
25
26
|
fileNames: Iterable<string>;
|
|
26
27
|
};
|
|
27
|
-
export declare function hasWorkspaceSignal(fileNames: Iterable<string>, packageJsonText?: string | null):
|
|
28
|
+
export declare function hasWorkspaceSignal(fileNames: Iterable<string>, packageJsonText?: string | null): boolean;
|
|
28
29
|
export declare function rankBuildAnalyzerCandidate(input: BuildAnalyzerCandidateRankInput): {
|
|
29
30
|
confidence: number;
|
|
30
31
|
reasons: string[];
|
|
@@ -43,6 +44,7 @@ export type PlatformConfig = {
|
|
|
43
44
|
redirects: Array<Record<string, unknown>>;
|
|
44
45
|
rewrites: Array<Record<string, unknown>>;
|
|
45
46
|
headers: Array<Record<string, unknown>>;
|
|
47
|
+
crons: Array<Record<string, unknown>>;
|
|
46
48
|
unsupportedFeatures: string[];
|
|
47
49
|
};
|
|
48
50
|
export declare function stringField(value: Record<string, unknown> | null, key: string): string | null;
|
|
@@ -51,7 +53,9 @@ export declare function detectPlatformConfigs(input: Omit<BuildSettingsInput, "f
|
|
|
51
53
|
export type PlatformRoutingConversion = {
|
|
52
54
|
redirectLines: string[];
|
|
53
55
|
headerBlocks: string[];
|
|
56
|
+
crons: SpaceCronConfig[];
|
|
54
57
|
notes: string[];
|
|
55
58
|
};
|
|
56
59
|
export declare function convertPlatformRouting(configs: PlatformConfig[]): PlatformRoutingConversion;
|
|
60
|
+
export declare const BUILD_MARKER_FILE_NAMES: Set<string>;
|
|
57
61
|
export declare function detectBuildSettings(input: BuildSettingsInput): DetectedBuildSettings;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { load as parseYaml } from "js-yaml";
|
|
2
2
|
import { parse as parseToml } from "smol-toml";
|
|
3
|
+
import { SPACE_CONFIG_CAPS, spaceCronKey, spaceCronSchema, } from "../contracts/space-config.js";
|
|
3
4
|
export const BUILD_ANALYZER_CANDIDATE_ROOTS = ["apps", "packages", "sites", "docs"];
|
|
4
5
|
const PACKAGE_MANAGER_ONLY_FILES = new Set([
|
|
5
6
|
"package.json",
|
|
@@ -12,12 +13,15 @@ const PACKAGE_MANAGER_ONLY_FILES = new Set([
|
|
|
12
13
|
"Gemfile",
|
|
13
14
|
"Gemfile.lock",
|
|
14
15
|
]);
|
|
16
|
+
const WORKSPACE_ROOT_FILES = new Set([
|
|
17
|
+
"pnpm-workspace.yaml",
|
|
18
|
+
"turbo.json",
|
|
19
|
+
"nx.json",
|
|
20
|
+
"lerna.json",
|
|
21
|
+
]);
|
|
15
22
|
export function hasWorkspaceSignal(fileNames, packageJsonText) {
|
|
16
23
|
const names = new Set(fileNames);
|
|
17
|
-
if (names.has(
|
|
18
|
-
names.has("turbo.json") ||
|
|
19
|
-
names.has("nx.json") ||
|
|
20
|
-
names.has("lerna.json")) {
|
|
24
|
+
if ([...WORKSPACE_ROOT_FILES].some((file) => names.has(file))) {
|
|
21
25
|
return true;
|
|
22
26
|
}
|
|
23
27
|
if (!packageJsonText) {
|
|
@@ -25,10 +29,14 @@ export function hasWorkspaceSignal(fileNames, packageJsonText) {
|
|
|
25
29
|
}
|
|
26
30
|
try {
|
|
27
31
|
const parsed = JSON.parse(packageJsonText);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
if (Array.isArray(parsed.workspaces)) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
if (!parsed.workspaces || typeof parsed.workspaces !== "object") {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
// SAFETY: The preceding non-null object check proves workspaces can carry an optional packages property.
|
|
39
|
+
return Array.isArray(parsed.workspaces.packages);
|
|
32
40
|
}
|
|
33
41
|
catch {
|
|
34
42
|
return false;
|
|
@@ -78,6 +86,7 @@ function parseJsonObject(value) {
|
|
|
78
86
|
}
|
|
79
87
|
try {
|
|
80
88
|
const parsed = JSON.parse(value);
|
|
89
|
+
// SAFETY: The conditional establishes parsed is a non-null, non-array object before returning it as a dictionary.
|
|
81
90
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
82
91
|
? parsed
|
|
83
92
|
: null;
|
|
@@ -98,6 +107,7 @@ function parseTomlObject(value) {
|
|
|
98
107
|
}
|
|
99
108
|
try {
|
|
100
109
|
const parsed = parseToml(value);
|
|
110
|
+
// SAFETY: The conditional establishes parsed is a non-null, non-array object before returning it as a dictionary.
|
|
101
111
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
102
112
|
? parsed
|
|
103
113
|
: null;
|
|
@@ -112,6 +122,7 @@ function parseYamlObject(value) {
|
|
|
112
122
|
}
|
|
113
123
|
try {
|
|
114
124
|
const parsed = parseYaml(value);
|
|
125
|
+
// SAFETY: The conditional establishes parsed is a non-null, non-array object before returning it as a dictionary.
|
|
115
126
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
116
127
|
? parsed
|
|
117
128
|
: null;
|
|
@@ -130,6 +141,7 @@ export function numberField(value, key) {
|
|
|
130
141
|
}
|
|
131
142
|
function objectField(value, key) {
|
|
132
143
|
const field = value?.[key];
|
|
144
|
+
// SAFETY: The conditional establishes field is a non-null, non-array object before returning it as a dictionary.
|
|
133
145
|
return field && typeof field === "object" && !Array.isArray(field)
|
|
134
146
|
? field
|
|
135
147
|
: null;
|
|
@@ -155,11 +167,11 @@ function vercelConfig(value) {
|
|
|
155
167
|
redirects: arrayField(config, "redirects"),
|
|
156
168
|
rewrites: arrayField(config, "rewrites"),
|
|
157
169
|
headers: arrayField(config, "headers"),
|
|
170
|
+
crons: arrayField(config, "crons"),
|
|
158
171
|
unsupportedFeatures: [
|
|
159
172
|
...("functions" in config ? ["Vercel functions are not converted."] : []),
|
|
160
173
|
...("middleware" in config ? ["Vercel middleware is not converted."] : []),
|
|
161
174
|
...("images" in config ? ["Vercel image optimization config is not converted."] : []),
|
|
162
|
-
...("crons" in config ? ["Vercel cron jobs are not converted."] : []),
|
|
163
175
|
],
|
|
164
176
|
};
|
|
165
177
|
}
|
|
@@ -179,6 +191,9 @@ function netlifyConfig(value) {
|
|
|
179
191
|
redirects: arrayField(config, "redirects"),
|
|
180
192
|
rewrites: [],
|
|
181
193
|
headers: arrayField(config, "headers"),
|
|
194
|
+
// Netlify has no scheduled-request config: its scheduled functions are
|
|
195
|
+
// declared in function code, which this importer does not read.
|
|
196
|
+
crons: [],
|
|
182
197
|
unsupportedFeatures: [
|
|
183
198
|
...("functions" in config ? ["Netlify functions are not converted."] : []),
|
|
184
199
|
...("edge_functions" in config ? ["Netlify edge functions are not converted."] : []),
|
|
@@ -224,6 +239,7 @@ function cloudflarePagesConfig(input) {
|
|
|
224
239
|
redirects: [],
|
|
225
240
|
rewrites: [],
|
|
226
241
|
headers: [],
|
|
242
|
+
crons: [],
|
|
227
243
|
unsupportedFeatures,
|
|
228
244
|
};
|
|
229
245
|
}
|
|
@@ -387,6 +403,7 @@ function convertVercelHeaderRule(rule, out) {
|
|
|
387
403
|
const entries = rule.headers;
|
|
388
404
|
const headers = (Array.isArray(entries) ? entries : [])
|
|
389
405
|
.map((entry) => {
|
|
406
|
+
// SAFETY: The conditional establishes entry is a non-null object before reading dictionary properties.
|
|
390
407
|
const header = entry && typeof entry === "object" ? entry : null;
|
|
391
408
|
const key = stringField(header, "key");
|
|
392
409
|
const value = header && typeof header.value === "string" ? header.value : null;
|
|
@@ -425,8 +442,47 @@ function convertNetlifyHeaderRule(rule, out) {
|
|
|
425
442
|
}
|
|
426
443
|
out.headerBlocks.push(headerBlock(matcher, headers));
|
|
427
444
|
}
|
|
445
|
+
/**
|
|
446
|
+
* One `vercel.json` cron entry into an `sf.jsonc` `crons` entry.
|
|
447
|
+
*
|
|
448
|
+
* Both sides are `{ path, schedule }` over a literal path and a five-field
|
|
449
|
+
* crontab expression, so a valid Vercel cron converts verbatim — there is no
|
|
450
|
+
* translation to get wrong. What this guards is the tail: Vercel accepts paths
|
|
451
|
+
* and schedules our grammar does not, and it has no per-project cap the way we
|
|
452
|
+
* do. Every rejection names the entry, because a scheduled job that silently
|
|
453
|
+
* stops existing is the one migration failure nobody notices until the work
|
|
454
|
+
* has not run for a week.
|
|
455
|
+
*/
|
|
456
|
+
function convertVercelCron(rule, out) {
|
|
457
|
+
const path = stringField(rule, "path");
|
|
458
|
+
const schedule = stringField(rule, "schedule");
|
|
459
|
+
const label = `vercel.json cron ${path ?? "(missing path)"}`;
|
|
460
|
+
if (!path || !schedule) {
|
|
461
|
+
out.notes.push(`${label}: skipped — missing path or schedule.`);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
const parsed = spaceCronSchema.safeParse({ path, schedule });
|
|
465
|
+
if (!parsed.success) {
|
|
466
|
+
out.notes.push(`${label}: skipped — ${parsed.error.issues.map((issue) => issue.message).join("; ")}`);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
if (out.crons.some((entry) => spaceCronKey(entry.path) === spaceCronKey(path))) {
|
|
470
|
+
out.notes.push(`${label}: skipped — another cron already schedules this path.`);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
if (out.crons.length >= SPACE_CONFIG_CAPS.crons) {
|
|
474
|
+
out.notes.push(`${label}: skipped — at most ${SPACE_CONFIG_CAPS.crons} crons are supported.`);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
out.crons.push(parsed.data);
|
|
478
|
+
}
|
|
428
479
|
export function convertPlatformRouting(configs) {
|
|
429
|
-
const out = {
|
|
480
|
+
const out = {
|
|
481
|
+
redirectLines: [],
|
|
482
|
+
headerBlocks: [],
|
|
483
|
+
crons: [],
|
|
484
|
+
notes: [],
|
|
485
|
+
};
|
|
430
486
|
for (const config of configs) {
|
|
431
487
|
if (config.platform === "vercel") {
|
|
432
488
|
for (const rule of config.redirects) {
|
|
@@ -438,6 +494,9 @@ export function convertPlatformRouting(configs) {
|
|
|
438
494
|
for (const rule of config.headers) {
|
|
439
495
|
convertVercelHeaderRule(rule, out);
|
|
440
496
|
}
|
|
497
|
+
for (const rule of config.crons) {
|
|
498
|
+
convertVercelCron(rule, out);
|
|
499
|
+
}
|
|
441
500
|
}
|
|
442
501
|
if (config.platform === "netlify") {
|
|
443
502
|
// Netlify expresses rewrites as 200-status redirects; both arrive in
|
|
@@ -671,6 +730,19 @@ const FRAMEWORKS = [
|
|
|
671
730
|
defaultBuildCommand: "bundle exec middleman build",
|
|
672
731
|
},
|
|
673
732
|
];
|
|
733
|
+
// Every file name the build analyzer keys on: package manifests, lockfiles,
|
|
734
|
+
// workspace roots, and the platform and framework configs it detects a build
|
|
735
|
+
// from. An archive carrying any of them is a project to build, not a website.
|
|
736
|
+
export const BUILD_MARKER_FILE_NAMES = new Set([
|
|
737
|
+
...PACKAGE_MANAGER_ONLY_FILES,
|
|
738
|
+
...WORKSPACE_ROOT_FILES,
|
|
739
|
+
"vercel.json",
|
|
740
|
+
"netlify.toml",
|
|
741
|
+
"wrangler.toml",
|
|
742
|
+
"wrangler.json",
|
|
743
|
+
"wrangler.jsonc",
|
|
744
|
+
...FRAMEWORKS.flatMap((framework) => framework.configFiles ?? []),
|
|
745
|
+
]);
|
|
674
746
|
function frameworkForPreset(frameworkPreset) {
|
|
675
747
|
return FRAMEWORKS.find((framework) => framework.preset === frameworkPreset) ?? null;
|
|
676
748
|
}
|
|
@@ -12,6 +12,7 @@ export function canonicalJson(value) {
|
|
|
12
12
|
return `[${value.map(canonicalJson).join(",")}]`;
|
|
13
13
|
}
|
|
14
14
|
if (value && typeof value === "object") {
|
|
15
|
+
// SAFETY: This branch establishes value is a non-null object before enumerating its entries.
|
|
15
16
|
return `{${Object.entries(value)
|
|
16
17
|
.toSorted(([left], [right]) => left.localeCompare(right))
|
|
17
18
|
.map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`)
|
|
@@ -10,6 +10,7 @@ export async function mapConcurrent(items, limit, mapper) {
|
|
|
10
10
|
if (index >= items.length) {
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
|
+
// SAFETY: the bounds check above proves this index addresses an item in the input array.
|
|
13
14
|
results[index] = await mapper(items[index], index);
|
|
14
15
|
await worker();
|
|
15
16
|
}
|
|
@@ -141,7 +141,7 @@ export function policyIsSubsetOfGrantor(requested, grantor) {
|
|
|
141
141
|
return statement.actions.every((action) => statement.resources.every((resource) => grantorAllows(grantor, action, resource, statement.conditions)));
|
|
142
142
|
}) && grantorDeniesArePreserved(requested, grantor));
|
|
143
143
|
}
|
|
144
|
-
// Request-time evaluation (
|
|
144
|
+
// Request-time evaluation (the platform spec IAM-lite): explicit deny wins,
|
|
145
145
|
// explicit allow permits, otherwise implicit deny. Condition keys are ANDed and an
|
|
146
146
|
// unresolvable condition attribute fails closed (see `conditionsMatch`).
|
|
147
147
|
export function policyAllows(input) {
|
|
@@ -152,7 +152,7 @@ export function policyAllows(input) {
|
|
|
152
152
|
return resources.some((resource) => matchesEffect(input.policy, "allow", input.action, resource, input.conditions, "request"));
|
|
153
153
|
}
|
|
154
154
|
// The tenants a credential's policy grants any access to. Drives tenant
|
|
155
|
-
// selection (
|
|
155
|
+
// selection (the platform spec Tenant): `X-Spacefast-Tenant-Id` may pick
|
|
156
156
|
// one granted tenant but is never an authority by itself, and a token spanning
|
|
157
157
|
// multiple tenants must fail ambiguous requests with `tenant_selection_required`.
|
|
158
158
|
export function grantedTenantsFromPolicy(policy) {
|
|
@@ -259,7 +259,7 @@ function resourceMatches(pattern, resource) {
|
|
|
259
259
|
if (pattern.endsWith("/*")) {
|
|
260
260
|
return resource === pattern.slice(0, -2) || resource.startsWith(pattern.slice(0, -1));
|
|
261
261
|
}
|
|
262
|
-
// A tenant-rooted resource is scope-wide (
|
|
262
|
+
// A tenant-rooted resource is scope-wide (the platform spec:
|
|
263
263
|
// `stt://tenant/{t}` is a grantable StatticResource and resource grants cover
|
|
264
264
|
// their subtree): it covers every resource inside that tenant. This is the
|
|
265
265
|
// tenant-scoped (partner/agency) credential shape.
|
|
@@ -273,6 +273,7 @@ function conditionsMatch(statementConditions, contextConditions, effect, mode) {
|
|
|
273
273
|
return true;
|
|
274
274
|
}
|
|
275
275
|
return Object.entries(statementConditions).every(([key, value]) => {
|
|
276
|
+
// SAFETY: Object.entries obtains key from the named RequestCondition object immediately above.
|
|
276
277
|
const resolved = contextConditions?.[key];
|
|
277
278
|
if (resolved === undefined && mode === "request") {
|
|
278
279
|
// Fail closed on unresolvable condition attributes: the condition never
|
|
@@ -15,15 +15,15 @@ export function addressDnsInstructionsForHostname(input) {
|
|
|
15
15
|
normalizeDnsName(instruction.name) !== hostname) {
|
|
16
16
|
return [];
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
];
|
|
18
|
+
const result = {
|
|
19
|
+
type: instruction.type,
|
|
20
|
+
name: instruction.name,
|
|
21
|
+
value: instruction.value,
|
|
22
|
+
purpose: instruction.purpose,
|
|
23
|
+
};
|
|
24
|
+
if (instruction.ttl !== undefined)
|
|
25
|
+
result.ttl = instruction.ttl;
|
|
26
|
+
return [result];
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
29
|
function normalizeAddress(value) {
|
package/dist/utils/error-code.js
CHANGED
package/dist/utils/grants.js
CHANGED
|
@@ -27,8 +27,10 @@ function targetMatches(selector, target) {
|
|
|
27
27
|
return target.kind === "version" && target.versionId === selector.versionId;
|
|
28
28
|
case "branch":
|
|
29
29
|
return target.kind === "branch" && target.branch === selector.branch;
|
|
30
|
+
// Immutable Version hosts only. The live host is its own target: sharing
|
|
31
|
+
// "every Version" does not thereby publish the live site.
|
|
30
32
|
case "all_versions":
|
|
31
|
-
return target.kind === "
|
|
33
|
+
return target.kind === "version";
|
|
32
34
|
}
|
|
33
35
|
throw new Error("Unsupported Grant target.");
|
|
34
36
|
}
|
|
@@ -22,6 +22,12 @@ export declare const localSpaceLinkSchema: z.ZodObject<{
|
|
|
22
22
|
apiBaseUrl: z.ZodOptional<z.ZodString>;
|
|
23
23
|
}, z.core.$loose>;
|
|
24
24
|
export declare const LOCAL_SPACE_STATE_SCHEMA_VERSION = 1;
|
|
25
|
+
export declare const creationAttemptSchema: z.ZodObject<{
|
|
26
|
+
key: z.ZodString;
|
|
27
|
+
principal: z.ZodString;
|
|
28
|
+
kind: z.ZodLiteral<"authenticated">;
|
|
29
|
+
createdAt: z.ZodString;
|
|
30
|
+
}, z.core.$strict>;
|
|
25
31
|
export declare const localSpaceStateSchema: z.ZodObject<{
|
|
26
32
|
schemaVersion: z.ZodLiteral<1>;
|
|
27
33
|
spaceId: z.ZodOptional<z.ZodString>;
|
|
@@ -41,22 +47,14 @@ export declare const localSpaceStateSchema: z.ZodObject<{
|
|
|
41
47
|
pendingPublish: z.ZodOptional<z.ZodObject<{
|
|
42
48
|
key: z.ZodString;
|
|
43
49
|
principal: z.ZodString;
|
|
44
|
-
kind: z.
|
|
45
|
-
anonymous: "anonymous";
|
|
46
|
-
authenticated: "authenticated";
|
|
47
|
-
}>;
|
|
48
|
-
intent: z.ZodOptional<z.ZodString>;
|
|
50
|
+
kind: z.ZodLiteral<"authenticated">;
|
|
49
51
|
createdAt: z.ZodString;
|
|
50
52
|
}, z.core.$strict>>;
|
|
51
53
|
/** Settled creation receipt identity retained for invocations that began before its commit. */
|
|
52
54
|
creationReplay: z.ZodOptional<z.ZodObject<{
|
|
53
55
|
key: z.ZodString;
|
|
54
56
|
principal: z.ZodString;
|
|
55
|
-
kind: z.
|
|
56
|
-
anonymous: "anonymous";
|
|
57
|
-
authenticated: "authenticated";
|
|
58
|
-
}>;
|
|
59
|
-
intent: z.ZodOptional<z.ZodString>;
|
|
57
|
+
kind: z.ZodLiteral<"authenticated">;
|
|
60
58
|
createdAt: z.ZodString;
|
|
61
59
|
}, z.core.$strict>>;
|
|
62
60
|
/** Random replay capability reserved before consuming a claim credential. */
|
|
@@ -147,7 +145,6 @@ export declare function mutateLocalSpaceState(root: string, mutate: (current: Lo
|
|
|
147
145
|
/** Fault-injection observer used to synchronize concurrency tests. */
|
|
148
146
|
onConflict?: (() => void | Promise<void>) | undefined;
|
|
149
147
|
}): Promise<LocalSpaceStateFile>;
|
|
150
|
-
export declare function writeLocalSpaceState(root: string, state: LocalSpaceStateWrite): Promise<string>;
|
|
151
148
|
/** Reserve random replay custody before a claim token can be consumed. */
|
|
152
149
|
export declare function reserveLocalCredentialReplay(root: string, input: {
|
|
153
150
|
operation: LocalCredentialReplayOperation;
|
|
@@ -29,6 +29,17 @@ export const localSpaceLinkSchema = z
|
|
|
29
29
|
})
|
|
30
30
|
.loose();
|
|
31
31
|
export const LOCAL_SPACE_STATE_SCHEMA_VERSION = 1;
|
|
32
|
+
// One reservation, two slots: it is written as `pendingPublish` and moved to
|
|
33
|
+
// `creationReplay` once its receipt commits. Space creation is the only
|
|
34
|
+
// mutation that reserves one — credential-free publishes are not idempotent.
|
|
35
|
+
export const creationAttemptSchema = z
|
|
36
|
+
.object({
|
|
37
|
+
key: z.string().min(1),
|
|
38
|
+
principal: z.string().regex(/^[0-9a-f]{64}$/),
|
|
39
|
+
kind: z.literal("authenticated"),
|
|
40
|
+
createdAt: z.string().datetime(),
|
|
41
|
+
})
|
|
42
|
+
.strict();
|
|
32
43
|
export const localSpaceStateSchema = z.strictObject({
|
|
33
44
|
schemaVersion: z.literal(LOCAL_SPACE_STATE_SCHEMA_VERSION),
|
|
34
45
|
spaceId: z.string().min(1).optional(),
|
|
@@ -45,27 +56,9 @@ export const localSpaceStateSchema = z.strictObject({
|
|
|
45
56
|
/** Secret per-checkout entropy used only to namespace replay-safe first publishes. */
|
|
46
57
|
anonymousPublishNonce: z.string().min(32).optional(),
|
|
47
58
|
/** Unsettled first-publish mutation. Retries must reuse this exact key. */
|
|
48
|
-
pendingPublish:
|
|
49
|
-
.object({
|
|
50
|
-
key: z.string().min(1),
|
|
51
|
-
principal: z.string().regex(/^[0-9a-f]{64}$/),
|
|
52
|
-
kind: z.enum(["anonymous", "authenticated"]),
|
|
53
|
-
intent: z.string().min(1).optional(),
|
|
54
|
-
createdAt: z.string().datetime(),
|
|
55
|
-
})
|
|
56
|
-
.strict()
|
|
57
|
-
.optional(),
|
|
59
|
+
pendingPublish: creationAttemptSchema.optional(),
|
|
58
60
|
/** Settled creation receipt identity retained for invocations that began before its commit. */
|
|
59
|
-
creationReplay:
|
|
60
|
-
.object({
|
|
61
|
-
key: z.string().min(1),
|
|
62
|
-
principal: z.string().regex(/^[0-9a-f]{64}$/),
|
|
63
|
-
kind: z.enum(["anonymous", "authenticated"]),
|
|
64
|
-
intent: z.string().min(1).optional(),
|
|
65
|
-
createdAt: z.string().datetime(),
|
|
66
|
-
})
|
|
67
|
-
.strict()
|
|
68
|
-
.optional(),
|
|
61
|
+
creationReplay: creationAttemptSchema.optional(),
|
|
69
62
|
/** Random replay capability reserved before consuming a claim credential. */
|
|
70
63
|
pendingCredentialReplay: z
|
|
71
64
|
.object({
|
|
@@ -360,10 +353,6 @@ export async function mutateLocalSpaceState(root, mutate, options = {}) {
|
|
|
360
353
|
/* eslint-enable no-await-in-loop */
|
|
361
354
|
throw new LocalSpaceStateConflictError("Local Spacefast state stayed busy for too long.");
|
|
362
355
|
}
|
|
363
|
-
export async function writeLocalSpaceState(root, state) {
|
|
364
|
-
await mutateLocalSpaceState(root, () => state);
|
|
365
|
-
return localSpaceStatePath(root);
|
|
366
|
-
}
|
|
367
356
|
function credentialFingerprint(credential) {
|
|
368
357
|
return createHash("sha256").update(credential).digest("hex");
|
|
369
358
|
}
|
|
@@ -11,4 +11,7 @@ export type OneShotReplayIdentity = {
|
|
|
11
11
|
*/
|
|
12
12
|
export declare function createOneShotReplayIdentity(prefix: string): OneShotReplayIdentity;
|
|
13
13
|
/** Production headers for one one-shot attempt (or one device-poll sequence). */
|
|
14
|
-
export declare function oneShotReplayHeaders(identity: OneShotReplayIdentity, sequence?: number):
|
|
14
|
+
export declare function oneShotReplayHeaders(identity: OneShotReplayIdentity, sequence?: number): {
|
|
15
|
+
"idempotency-key": string;
|
|
16
|
+
"x-spacefast-idempotency-principal": string;
|
|
17
|
+
};
|