@sequenceholdings/artifact-studio 0.4.0 → 0.6.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.
Files changed (114) hide show
  1. package/README.md +35 -1
  2. package/VERSION-PIN.md +29 -0
  3. package/dist/active-deploy-policy.d.ts +9 -3
  4. package/dist/active-deploy-policy.js +20 -8
  5. package/dist/artifact-env-bindings.d.ts +9 -0
  6. package/dist/artifact-env-bindings.js +32 -0
  7. package/dist/build.js +6 -0
  8. package/dist/cli.js +62 -13
  9. package/dist/deployment-validation.d.ts +2 -0
  10. package/dist/deployment-validation.js +2 -0
  11. package/dist/dev-link.js +28 -2
  12. package/dist/env-binding-scan.d.ts +10 -0
  13. package/dist/env-binding-scan.js +179 -0
  14. package/dist/env-bindings.d.ts +52 -0
  15. package/dist/env-bindings.js +180 -0
  16. package/dist/gateway/control.d.ts +33 -0
  17. package/dist/gateway/control.js +29 -0
  18. package/dist/gateway/hosted-proxy.d.ts +5 -0
  19. package/dist/gateway/hosted-proxy.js +4 -1
  20. package/dist/gateway/lattice-invoke.d.ts +48 -0
  21. package/dist/gateway/lattice-invoke.js +245 -0
  22. package/dist/gateway/lattice-upstream.d.ts +38 -0
  23. package/dist/gateway/lattice-upstream.js +136 -0
  24. package/dist/gateway/server.js +2 -3
  25. package/dist/gateway/session.d.ts +3 -1
  26. package/dist/gateway/session.js +10 -3
  27. package/dist/manifest-next.d.ts +55 -0
  28. package/dist/manifest-next.js +35 -2
  29. package/dist/manifest-static.d.ts +35 -0
  30. package/dist/manifest-static.js +12 -0
  31. package/dist/manifest.d.ts +48 -0
  32. package/dist/next-build.d.ts +2 -1
  33. package/dist/next-build.js +12 -7
  34. package/dist/offline/cache.d.ts +4 -0
  35. package/dist/offline/cache.js +7 -0
  36. package/dist/offline/connector.d.ts +28 -0
  37. package/dist/offline/connector.js +122 -0
  38. package/dist/offline/crypto.d.ts +5 -0
  39. package/dist/offline/crypto.js +24 -0
  40. package/dist/offline/index.d.ts +8 -0
  41. package/dist/offline/index.js +7 -0
  42. package/dist/offline/indexed-db.d.ts +10 -0
  43. package/dist/offline/indexed-db.js +104 -0
  44. package/dist/offline/management-types.d.ts +25 -0
  45. package/dist/offline/management-types.js +1 -0
  46. package/dist/offline/outbox.d.ts +7 -0
  47. package/dist/offline/outbox.js +46 -0
  48. package/dist/offline/replay.d.ts +25 -0
  49. package/dist/offline/replay.js +192 -0
  50. package/dist/offline/secure-store.d.ts +6 -0
  51. package/dist/offline/secure-store.js +51 -0
  52. package/dist/offline/types.d.ts +162 -0
  53. package/dist/offline/types.js +1 -0
  54. package/dist/orm-json-scalars.d.ts +21 -0
  55. package/dist/orm-json-scalars.js +21 -0
  56. package/dist/runtime-contracts.d.ts +7 -7
  57. package/dist/runtime-contracts.js +1 -1
  58. package/dist/sdk-orm.d.ts +7 -1
  59. package/dist/sdk-orm.js +12 -5
  60. package/dist/sdk.d.ts +134 -1
  61. package/dist/server/api-client.d.ts +26 -0
  62. package/dist/server/api-client.js +38 -0
  63. package/dist/server/broker-contract.d.ts +191 -5
  64. package/dist/server/broker-contract.js +142 -1
  65. package/dist/server/client.d.ts +82 -47
  66. package/dist/server/client.js +138 -59
  67. package/dist/server/connection-retry.d.ts +12 -0
  68. package/dist/server/connection-retry.js +40 -0
  69. package/dist/server/errors.d.ts +61 -0
  70. package/dist/server/errors.js +89 -0
  71. package/dist/server/external-context.d.ts +21 -0
  72. package/dist/server/external-context.js +19 -0
  73. package/dist/server/internal/signed-token.d.ts +15 -0
  74. package/dist/server/internal/signed-token.js +67 -0
  75. package/dist/server/internal/v1-context.d.ts +18 -0
  76. package/dist/server/internal/v1-context.js +26 -0
  77. package/dist/server/internal/verify-any.d.ts +22 -0
  78. package/dist/server/internal/verify-any.js +53 -0
  79. package/dist/server/logger.d.ts +1 -1
  80. package/dist/server/managed-server-path.d.ts +6 -0
  81. package/dist/server/managed-server-path.js +14 -0
  82. package/dist/server/principal.d.ts +48 -0
  83. package/dist/server/principal.js +39 -0
  84. package/dist/server/processes.d.ts +290 -0
  85. package/dist/server/processes.js +56 -0
  86. package/dist/server/request-context.d.ts +121 -3
  87. package/dist/server/request-context.js +111 -33
  88. package/dist/server/testing.d.ts +40 -0
  89. package/dist/server/testing.js +86 -0
  90. package/dist/server.d.ts +13 -4
  91. package/dist/server.js +6 -2
  92. package/dist/source-resolver.d.ts +9 -2
  93. package/dist/source-resolver.js +55 -8
  94. package/dist/templates/nextjs/AGENTS.md +10 -0
  95. package/dist/templates/nextjs/CLAUDE.md +1 -0
  96. package/dist/templates/nextjs/README.md +88 -0
  97. package/dist/templates/nextjs/package.json +3 -1
  98. package/dist/templates/nextjs/tests/server.test.mjs +25 -0
  99. package/dist/templates/react-vite/AGENTS.md +8 -88
  100. package/dist/templates/react-vite/CLAUDE.md +0 -6
  101. package/dist/templates/react-vite/package.json +1 -1
  102. package/dist/templates/react-vite/src/App.tsx +37 -14
  103. package/dist/templates/react-vite/src/styles.css +10 -0
  104. package/package.json +15 -3
  105. package/templates/nextjs/AGENTS.md +10 -0
  106. package/templates/nextjs/CLAUDE.md +1 -0
  107. package/templates/nextjs/README.md +88 -0
  108. package/templates/nextjs/package.json +3 -1
  109. package/templates/nextjs/tests/server.test.mjs +25 -0
  110. package/templates/react-vite/AGENTS.md +8 -88
  111. package/templates/react-vite/CLAUDE.md +0 -6
  112. package/templates/react-vite/package.json +1 -1
  113. package/templates/react-vite/src/App.tsx +37 -14
  114. package/templates/react-vite/src/styles.css +10 -0
@@ -1,7 +1,9 @@
1
1
  import { storageScopesSchema } from '@sequenceholdings/storage';
2
+ import { ontologyContractSchema } from '@sequenceholdings/orm/canonical';
2
3
  import { z } from 'zod';
3
4
  import { capabilityPinsSchema, customRolesBlockSchema, validateCapabilityPins } from './manifest-custom-roles.js';
4
- import { apiCapabilityPathAllowed, artifactStudioStaticManifestSchema, sidebarBadgeSchema } from './manifest-static.js';
5
+ import { isManagedServerInvokePath } from './server/managed-server-path.js';
6
+ import { apiCapabilityPathAllowed, artifactPresentationSchema, artifactStudioStaticManifestSchema, sidebarBadgeSchema, } from './manifest-static.js';
5
7
  /** Approved major/minor profile. Builds additionally record exact patch versions. */
6
8
  export const ARTIFACT_NEXT_RUNTIME = Object.freeze({ sdk: 'sequence', node: 24, react: 19, next: '16.3' });
7
9
  /** Admission policy for new development sessions and builds; retained releases keep their recorded toolchain. */
@@ -30,13 +32,34 @@ const ormTableRef = /^[a-z][a-z0-9_]{0,40}\/(?:[a-z][a-z0-9_]{0,62}|platformUser
30
32
  const ormOperationRef = /^[a-z][a-z0-9_]{0,40}\/ops\/[_A-Za-z][_0-9A-Za-z]*$/;
31
33
  const dataReadRef = boundedRef.refine((ref) => ormTableRef.test(ref), 'ORM read references use namespace/table');
32
34
  const dataWriteRef = boundedRef.refine((ref) => ormTableRef.test(ref) || ormOperationRef.test(ref), 'ORM write references use namespace/ops/Operation or namespace/table');
35
+ const latticeProcessRef = z.string().min(2).max(128).regex(/^[A-Za-z0-9._-]+$/, 'Process references are Lattice process ids');
33
36
  const reachShape = {
34
37
  storage: storageScopesSchema.optional(),
35
38
  data: z.strictObject({ read: z.array(dataReadRef).max(256).default([]), write: z.array(dataWriteRef).max(256).default([]) }).prefault({}),
36
39
  api: z.strictObject({ read: z.array(apiPath).max(256).default([]), write: z.array(apiPath).max(256).default([]) }).prefault({}),
37
40
  functions: z.strictObject({ invoke: z.array(boundedRef).max(256).default([]) }).prefault({}),
41
+ // `start`: processes the server may start runs of. `read`: processes whose runs, template and
42
+ // the user's own steps it may read. `act`: processes whose human steps it may advance and
43
+ // reassign, and whose runs' viewers it may manage. All name exact process ids; the user's own
44
+ // Invoker / Viewer / Advancer / Owner grants decide what happens.
45
+ processes: z.strictObject({
46
+ start: z.array(latticeProcessRef).max(256).default([]),
47
+ read: z.array(latticeProcessRef).max(256).default([]),
48
+ act: z.array(latticeProcessRef).max(256).default([]),
49
+ }).prefault({}),
38
50
  };
39
- export const artifactNextServerCapabilitiesSchema = z.strictObject(reachShape).prefault({});
51
+ /**
52
+ * Where the server's audit events (`client.audit.record`) go: an Atlas audit service id. Every
53
+ * application may record under `artifact_studio`; Atlas admits a first-party service, such as
54
+ * `application_portal`, only for the projects it registers for it. Server-only: browsers can't audit.
55
+ */
56
+ export const artifactNextAuditCapabilitySchema = z.strictObject({
57
+ service: z.string().regex(/^[a-z][a-z0-9_]{0,63}$/, 'Audit services are Atlas audit service ids, such as artifact_studio'),
58
+ });
59
+ export const artifactNextServerCapabilitiesSchema = z.strictObject({
60
+ ...reachShape,
61
+ audit: artifactNextAuditCapabilitySchema.optional(),
62
+ }).prefault({});
40
63
  // Reuse role semantics and validation; only the new manifest rejects unknown keys.
41
64
  const strictCustomRoles = customRolesBlockSchema.safeExtend({
42
65
  definitions: z.record(customRolesBlockSchema.shape.definitions.unwrap().keyType, customRolesBlockSchema.shape.definitions.unwrap().valueType.strict()).default({}),
@@ -83,6 +106,7 @@ export const artifactNextBuildConfigSchema = z.strictObject({
83
106
  public_fonts: z.array(artifactNextPublicFontSchema).max(8).refine((fonts) => new Set(fonts.map((font) => font.path)).size === fonts.length, 'Public font destination paths must be unique').default([]),
84
107
  }).prefault({});
85
108
  const hostname = z.string().max(253).regex(/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?$/, 'Egress entries must be exact lowercase DNS hostnames without schemes, ports, paths, IPs, or wildcards').refine((host) => !host.endsWith('.localhost') && !host.endsWith('.local') && !host.endsWith('.internal'), 'Egress cannot target local or platform-internal hostnames');
109
+ const managedServerInvokePath = z.string().refine(isManagedServerInvokePath, 'Lattice accept paths are exact origin-relative paths with no query or traversal');
86
110
  export const artifactNextServerSchema = z.strictObject({
87
111
  capabilities: artifactNextServerCapabilitiesSchema,
88
112
  service_account: serviceAccountRef.optional(),
@@ -90,6 +114,10 @@ export const artifactNextServerSchema = z.strictObject({
90
114
  egress: z.array(hostname).max(64).default([]),
91
115
  resources: artifactNextResourceLimitsSchema,
92
116
  cache: z.strictObject({ capacity_gb: z.number().int().min(1).max(16) }).optional(),
117
+ /** Paths Lattice may POST. Exact match only; omitted means Lattice cannot call this server. */
118
+ lattice: z.strictObject({
119
+ accept: z.array(managedServerInvokePath).max(32).default([]),
120
+ }).optional(),
93
121
  }).superRefine((server, ctx) => {
94
122
  if (new Set(server.secrets.map((secret) => secret.name)).size !== server.secrets.length) {
95
123
  ctx.addIssue({ code: 'custom', path: ['secrets'], message: 'Each secret environment name must be unique' });
@@ -97,6 +125,9 @@ export const artifactNextServerSchema = z.strictObject({
97
125
  if (new Set(server.egress).size !== server.egress.length) {
98
126
  ctx.addIssue({ code: 'custom', path: ['egress'], message: 'Egress hosts must be unique' });
99
127
  }
128
+ if (server.lattice && new Set(server.lattice.accept).size !== server.lattice.accept.length) {
129
+ ctx.addIssue({ code: 'custom', path: ['lattice', 'accept'], message: 'Lattice accept paths must be unique' });
130
+ }
100
131
  }).prefault({});
101
132
  const appRoot = z.string().min(1).max(512).refine((value) => {
102
133
  if (value === '.')
@@ -105,6 +136,7 @@ const appRoot = z.string().min(1).max(512).refine((value) => {
105
136
  value.split('/').every((part) => part !== '' && part !== '.' && part !== '..' && !part.startsWith('.') && part !== 'node_modules');
106
137
  }, 'app_root must be a relative source directory without traversal, hidden directories, or node_modules').default('.');
107
138
  export const artifactStudioNextManifestSchema = z.strictObject({
139
+ ontology: ontologyContractSchema.optional(),
108
140
  bundle: z.strictObject({ name: z.string().min(1).max(255), schema_version: z.literal(2) }),
109
141
  artifact: z.strictObject({
110
142
  project_id: artifactStudioStaticManifestSchema.shape.artifact.shape.project_id,
@@ -120,6 +152,7 @@ export const artifactStudioNextManifestSchema = z.strictObject({
120
152
  build: artifactNextBuildConfigSchema,
121
153
  server: artifactNextServerSchema,
122
154
  targets: z.record(z.string().min(1), z.strictObject({ url: z.string().url() })).default({}),
155
+ presentation: artifactPresentationSchema.optional(),
123
156
  sidebarIcon: z.string().max(64).optional(),
124
157
  sidebarBadge: sidebarBadgeSchema.strict().optional(),
125
158
  }).superRefine((manifest, ctx) => {
@@ -8,8 +8,30 @@ export declare const sidebarBadgeSchema: z.ZodObject<{
8
8
  }>>;
9
9
  }, z.core.$strip>;
10
10
  export type SidebarBadgeManifest = z.infer<typeof sidebarBadgeSchema>;
11
+ export declare const artifactPresentationSchema: z.ZodObject<{
12
+ mobile: z.ZodOptional<z.ZodEnum<{
13
+ responsive: "responsive";
14
+ }>>;
15
+ }, z.core.$strip>;
16
+ export type ArtifactPresentation = z.infer<typeof artifactPresentationSchema>;
17
+ export declare const artifactOfflineCapabilitySchema: z.ZodObject<{
18
+ mode: z.ZodEnum<{
19
+ read: "read";
20
+ "read-write": "read-write";
21
+ }>;
22
+ max_age_hours: z.ZodNumber;
23
+ policy_version: z.ZodNumber;
24
+ }, z.core.$strip>;
25
+ export type ArtifactOfflineCapability = z.infer<typeof artifactOfflineCapabilitySchema>;
11
26
  export declare function apiCapabilityPathAllowed(pathname: string, allowed: readonly string[]): boolean;
12
27
  export declare const artifactStudioStaticManifestSchema: z.ZodObject<{
28
+ ontology: z.ZodOptional<z.ZodObject<{
29
+ version: z.ZodLiteral<1>;
30
+ read: z.ZodDefault<z.ZodArray<z.ZodString>>;
31
+ write: z.ZodDefault<z.ZodArray<z.ZodString>>;
32
+ produce: z.ZodDefault<z.ZodArray<z.ZodString>>;
33
+ invoke: z.ZodDefault<z.ZodArray<z.ZodString>>;
34
+ }, z.core.$strict>>;
13
35
  bundle: z.ZodObject<{
14
36
  name: z.ZodString;
15
37
  schema_version: z.ZodDefault<z.ZodLiteral<1>>;
@@ -63,6 +85,19 @@ export declare const artifactStudioStaticManifestSchema: z.ZodObject<{
63
85
  targets: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
64
86
  url: z.ZodString;
65
87
  }, z.core.$strict>>>;
88
+ presentation: z.ZodOptional<z.ZodObject<{
89
+ mobile: z.ZodOptional<z.ZodEnum<{
90
+ responsive: "responsive";
91
+ }>>;
92
+ }, z.core.$strip>>;
93
+ offline: z.ZodOptional<z.ZodObject<{
94
+ mode: z.ZodEnum<{
95
+ read: "read";
96
+ "read-write": "read-write";
97
+ }>;
98
+ max_age_hours: z.ZodNumber;
99
+ policy_version: z.ZodNumber;
100
+ }, z.core.$strip>>;
66
101
  sidebarIcon: z.ZodOptional<z.ZodString>;
67
102
  sidebarBadge: z.ZodOptional<z.ZodObject<{
68
103
  endpoint: z.ZodString;
@@ -1,4 +1,5 @@
1
1
  import { storageScopesSchema } from '@sequenceholdings/storage';
2
+ import { ontologyContractSchema } from '@sequenceholdings/orm/canonical';
2
3
  import { z } from 'zod';
3
4
  import { capabilityPinsSchema, customRolesBlockSchema, validateCapabilityPins, } from './manifest-custom-roles.js';
4
5
  import { normalizeArtifactPath } from './paths.js';
@@ -13,6 +14,14 @@ export const sidebarBadgeSchema = z.object({
13
14
  endpoint: apiCapabilityPathSchema,
14
15
  tone: sidebarBadgeToneSchema.optional(),
15
16
  });
17
+ export const artifactPresentationSchema = z.object({
18
+ mobile: z.enum(['responsive']).optional(),
19
+ });
20
+ export const artifactOfflineCapabilitySchema = z.object({
21
+ mode: z.enum(['read', 'read-write']),
22
+ max_age_hours: z.number().int().min(1),
23
+ policy_version: z.number().int().min(1),
24
+ });
16
25
  function matchesApiCapabilityEntry(pathname, entry) {
17
26
  if (!entry.startsWith('/api/'))
18
27
  return false;
@@ -41,6 +50,7 @@ export function apiCapabilityPathAllowed(pathname, allowed) {
41
50
  return allowed.some((entry) => matchesApiCapabilityEntry(pathname, entry));
42
51
  }
43
52
  export const artifactStudioStaticManifestSchema = z.object({
53
+ ontology: ontologyContractSchema.optional(),
44
54
  bundle: z.object({
45
55
  name: z.string().min(1).max(255),
46
56
  schema_version: z.literal(1).default(1),
@@ -99,6 +109,8 @@ export const artifactStudioStaticManifestSchema = z.object({
99
109
  targets: z.record(z.string().min(1), z.object({
100
110
  url: z.string().url(),
101
111
  }).strict()).default({}),
112
+ presentation: artifactPresentationSchema.optional(),
113
+ offline: artifactOfflineCapabilitySchema.optional(),
102
114
  sidebarIcon: z.string().max(64).optional(),
103
115
  sidebarBadge: sidebarBadgeSchema.optional(),
104
116
  }).superRefine((manifest, ctx) => {
@@ -5,6 +5,13 @@ export * from './manifest-static.js';
5
5
  export * from './manifest-next.js';
6
6
  /** Versioned parsing keeps schema-v1 stripping/default behavior unchanged. */
7
7
  export declare const artifactStudioManifestSchema: z.ZodUnion<readonly [z.ZodObject<{
8
+ ontology: z.ZodOptional<z.ZodObject<{
9
+ version: z.ZodLiteral<1>;
10
+ read: z.ZodDefault<z.ZodArray<z.ZodString>>;
11
+ write: z.ZodDefault<z.ZodArray<z.ZodString>>;
12
+ produce: z.ZodDefault<z.ZodArray<z.ZodString>>;
13
+ invoke: z.ZodDefault<z.ZodArray<z.ZodString>>;
14
+ }, z.core.$strict>>;
8
15
  bundle: z.ZodObject<{
9
16
  name: z.ZodString;
10
17
  schema_version: z.ZodDefault<z.ZodLiteral<1>>;
@@ -58,6 +65,19 @@ export declare const artifactStudioManifestSchema: z.ZodUnion<readonly [z.ZodObj
58
65
  targets: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
59
66
  url: z.ZodString;
60
67
  }, z.core.$strict>>>;
68
+ presentation: z.ZodOptional<z.ZodObject<{
69
+ mobile: z.ZodOptional<z.ZodEnum<{
70
+ responsive: "responsive";
71
+ }>>;
72
+ }, z.core.$strip>>;
73
+ offline: z.ZodOptional<z.ZodObject<{
74
+ mode: z.ZodEnum<{
75
+ read: "read";
76
+ "read-write": "read-write";
77
+ }>;
78
+ max_age_hours: z.ZodNumber;
79
+ policy_version: z.ZodNumber;
80
+ }, z.core.$strip>>;
61
81
  sidebarIcon: z.ZodOptional<z.ZodString>;
62
82
  sidebarBadge: z.ZodOptional<z.ZodObject<{
63
83
  endpoint: z.ZodString;
@@ -67,6 +87,13 @@ export declare const artifactStudioManifestSchema: z.ZodUnion<readonly [z.ZodObj
67
87
  }>>;
68
88
  }, z.core.$strip>>;
69
89
  }, z.core.$strip>, z.ZodObject<{
90
+ ontology: z.ZodOptional<z.ZodObject<{
91
+ version: z.ZodLiteral<1>;
92
+ read: z.ZodDefault<z.ZodArray<z.ZodString>>;
93
+ write: z.ZodDefault<z.ZodArray<z.ZodString>>;
94
+ produce: z.ZodDefault<z.ZodArray<z.ZodString>>;
95
+ invoke: z.ZodDefault<z.ZodArray<z.ZodString>>;
96
+ }, z.core.$strict>>;
70
97
  bundle: z.ZodObject<{
71
98
  name: z.ZodString;
72
99
  schema_version: z.ZodLiteral<2>;
@@ -115,6 +142,11 @@ export declare const artifactStudioManifestSchema: z.ZodUnion<readonly [z.ZodObj
115
142
  functions: z.ZodPrefault<z.ZodObject<{
116
143
  invoke: z.ZodDefault<z.ZodArray<z.ZodString>>;
117
144
  }, z.core.$strict>>;
145
+ processes: z.ZodPrefault<z.ZodObject<{
146
+ start: z.ZodDefault<z.ZodArray<z.ZodString>>;
147
+ read: z.ZodDefault<z.ZodArray<z.ZodString>>;
148
+ act: z.ZodDefault<z.ZodArray<z.ZodString>>;
149
+ }, z.core.$strict>>;
118
150
  }, z.core.$strict>>;
119
151
  build: z.ZodPrefault<z.ZodObject<{
120
152
  public_env: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
@@ -126,6 +158,9 @@ export declare const artifactStudioManifestSchema: z.ZodUnion<readonly [z.ZodObj
126
158
  }, z.core.$strict>>;
127
159
  server: z.ZodPrefault<z.ZodObject<{
128
160
  capabilities: z.ZodPrefault<z.ZodObject<{
161
+ audit: z.ZodOptional<z.ZodObject<{
162
+ service: z.ZodString;
163
+ }, z.core.$strict>>;
129
164
  storage: z.ZodOptional<z.ZodRecord<z.ZodUUID, z.ZodEnum<{
130
165
  viewer: "viewer";
131
166
  editor: "editor";
@@ -142,6 +177,11 @@ export declare const artifactStudioManifestSchema: z.ZodUnion<readonly [z.ZodObj
142
177
  functions: z.ZodPrefault<z.ZodObject<{
143
178
  invoke: z.ZodDefault<z.ZodArray<z.ZodString>>;
144
179
  }, z.core.$strict>>;
180
+ processes: z.ZodPrefault<z.ZodObject<{
181
+ start: z.ZodDefault<z.ZodArray<z.ZodString>>;
182
+ read: z.ZodDefault<z.ZodArray<z.ZodString>>;
183
+ act: z.ZodDefault<z.ZodArray<z.ZodString>>;
184
+ }, z.core.$strict>>;
145
185
  }, z.core.$strict>>;
146
186
  service_account: z.ZodOptional<z.ZodString>;
147
187
  secrets: z.ZodDefault<z.ZodArray<z.ZodObject<{
@@ -161,10 +201,18 @@ export declare const artifactStudioManifestSchema: z.ZodUnion<readonly [z.ZodObj
161
201
  cache: z.ZodOptional<z.ZodObject<{
162
202
  capacity_gb: z.ZodNumber;
163
203
  }, z.core.$strict>>;
204
+ lattice: z.ZodOptional<z.ZodObject<{
205
+ accept: z.ZodDefault<z.ZodArray<z.ZodString>>;
206
+ }, z.core.$strict>>;
164
207
  }, z.core.$strict>>;
165
208
  targets: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
166
209
  url: z.ZodString;
167
210
  }, z.core.$strict>>>;
211
+ presentation: z.ZodOptional<z.ZodObject<{
212
+ mobile: z.ZodOptional<z.ZodEnum<{
213
+ responsive: "responsive";
214
+ }>>;
215
+ }, z.core.$strip>>;
168
216
  sidebarIcon: z.ZodOptional<z.ZodString>;
169
217
  sidebarBadge: z.ZodOptional<z.ZodObject<{
170
218
  endpoint: z.ZodString;
@@ -15,8 +15,9 @@ export declare function resolveNextRepositoryId({ spec, repository, baseUrl, tok
15
15
  baseUrl: string;
16
16
  token: string;
17
17
  }): Promise<string>;
18
- export declare function submitNextBuild({ root, manifest, projectId, baseUrl, getToken, provenance, repository, repositoryId, channel, out }: {
18
+ export declare function submitNextBuild({ root, sourceRoot, manifest, projectId, baseUrl, getToken, provenance, repository, repositoryId, channel, out }: {
19
19
  root: string;
20
+ sourceRoot: string;
20
21
  manifest: ArtifactStudioNextManifest;
21
22
  projectId: string;
22
23
  baseUrl: string;
@@ -58,12 +58,16 @@ export async function resolveNextRepositoryId({ spec, repository, baseUrl, token
58
58
  return match[1];
59
59
  }
60
60
  catch { /* Opaque labels/SSH origins are valid preview provenance but cannot attest a release. */ }
61
- throw new Error('Official Next.js releases require a Git Service repository in this Atlas environment. Push the reviewed revision, then run artifact deploy --repo <namespace>/<name> --ref main --env <env>.');
61
+ throw new Error('Official Next.js releases require a Git Service repository in this Atlas environment. Push the reviewed revision, then run artifact deploy --repo <namespace>/<name> --ref main --env <env> (or --ref staging for a staging environment).');
62
62
  }
63
- /** A verified repository build names a commit; anything else uploads the committed source snapshot. */
64
- function nextBuildRequestBody({ manifest, files, commit, provenance, repository, repositoryId, channel }) {
63
+ /**
64
+ * A verified repository build names a commit and the application's directory in it;
65
+ * anything else uploads the committed source snapshot. The repository root is omitted
66
+ * so the request stays valid for Atlas versions that predate `sourceRoot`.
67
+ */
68
+ function nextBuildRequestBody({ manifest, files, commit, provenance, repository, repositoryId, sourceRoot, channel }) {
65
69
  if (repositoryId)
66
- return { repositoryId, commit, idempotencyKey: randomUUID(), channel };
70
+ return { repositoryId, commit, ...(sourceRoot === '.' ? {} : { sourceRoot }), idempotencyKey: randomUUID(), channel };
67
71
  return {
68
72
  manifest,
69
73
  files: files.map(({ path, content, encoding }) => ({ path, content, encoding: encoding ?? 'utf8' })),
@@ -71,7 +75,7 @@ function nextBuildRequestBody({ manifest, files, commit, provenance, repository,
71
75
  idempotencyKey: randomUUID(), channel,
72
76
  };
73
77
  }
74
- export async function submitNextBuild({ root, manifest, projectId, baseUrl, getToken, provenance, repository, repositoryId, channel, out }) {
78
+ export async function submitNextBuild({ root, sourceRoot, manifest, projectId, baseUrl, getToken, provenance, repository, repositoryId, channel, out }) {
75
79
  if (!provenance.gitCommit || !/^[a-f0-9]{40}$/i.test(provenance.gitCommit)) {
76
80
  throw new Error('Commit the application source before requesting a hosted build. Use artifact dev for uncommitted local development.');
77
81
  }
@@ -80,9 +84,10 @@ export async function submitNextBuild({ root, manifest, projectId, baseUrl, getT
80
84
  if (channel === 'active' && !repositoryId)
81
85
  throw new Error('An official release requires a verified Git Service repository.');
82
86
  const path = `/api/artifact-studio/projects/${encodeURIComponent(projectId)}/next`;
83
- const body = nextBuildRequestBody({ manifest, files: source.files, commit: provenance.gitCommit, provenance, repository, repositoryId, channel });
87
+ const body = nextBuildRequestBody({ manifest, files: source.files, commit: provenance.gitCommit, provenance, repository, repositoryId, sourceRoot, channel });
84
88
  const response = operationSchema.parse(await postJson({ baseUrl, token: await getToken(), path: `${path}/builds${repositoryId ? '/from-repository' : ''}`, body }));
85
- console.log(`[seq-studio] Atlas build ${response.operation.id} (${channel}) from ${provenance.gitCommit.slice(0, 12)}`);
89
+ const directory = repositoryId && sourceRoot !== '.' ? ` (${sourceRoot})` : '';
90
+ console.log(`[seq-studio] Atlas build ${response.operation.id} (${channel}) from ${provenance.gitCommit.slice(0, 12)}${directory}`);
86
91
  console.log(`${baseUrl}/artifact-studio/projects/${projectId}`);
87
92
  const controller = new AbortController();
88
93
  const onSignal = () => controller.abort();
@@ -0,0 +1,4 @@
1
+ import type { EncryptedOfflineStore, OfflineCache } from './types.js';
2
+ export declare function createOfflineCache({ store, }: {
3
+ store: EncryptedOfflineStore;
4
+ }): OfflineCache;
@@ -0,0 +1,7 @@
1
+ export function createOfflineCache({ store, }) {
2
+ return {
3
+ read: ({ key }) => store.get({ store: 'cache', key }),
4
+ write: ({ key, entry }) => store.set({ store: 'cache', key, value: entry }),
5
+ remove: ({ key }) => store.delete({ store: 'cache', key }),
6
+ };
7
+ }
@@ -0,0 +1,28 @@
1
+ import type { SequenceOfflineRequest, SequenceOfflineRequestConnector } from '../sdk.js';
2
+ import type { OfflineReplayEngine } from './replay.js';
3
+ import type { OfflineCache, OfflineMutationPolicy, OfflineReadPolicy } from './types.js';
4
+ export declare class OfflineDataUnavailableError extends Error {
5
+ readonly path: string;
6
+ constructor(path: string);
7
+ }
8
+ export declare class OfflineWriteBlockedError extends Error {
9
+ readonly path: string;
10
+ constructor(path: string);
11
+ }
12
+ export interface ArtifactOfflineConnector {
13
+ connector: SequenceOfflineRequestConnector;
14
+ flush(): Promise<void>;
15
+ }
16
+ export declare function createArtifactOfflineConnector({ cache, readPolicy, mutationPolicy, outbox, replay, isOnline, clock, onCacheWriteError, }: {
17
+ cache: OfflineCache;
18
+ readPolicy: OfflineReadPolicy;
19
+ mutationPolicy: OfflineMutationPolicy;
20
+ outbox: import('./types.js').OfflineOutbox;
21
+ replay: OfflineReplayEngine;
22
+ isOnline: () => boolean;
23
+ clock?: () => number;
24
+ onCacheWriteError?: (input: {
25
+ request: SequenceOfflineRequest;
26
+ error: unknown;
27
+ }) => void;
28
+ }): ArtifactOfflineConnector;
@@ -0,0 +1,122 @@
1
+ export class OfflineDataUnavailableError extends Error {
2
+ path;
3
+ constructor(path) {
4
+ super('No offline data available');
5
+ this.name = 'OfflineDataUnavailableError';
6
+ this.path = path;
7
+ }
8
+ }
9
+ export class OfflineWriteBlockedError extends Error {
10
+ path;
11
+ constructor(path) {
12
+ super('Reconnect to complete this action');
13
+ this.name = 'OfflineWriteBlockedError';
14
+ this.path = path;
15
+ }
16
+ }
17
+ function canUseCachedFallback(response) {
18
+ return response.status === 408 || response.status === 429 || response.status >= 500;
19
+ }
20
+ export function createArtifactOfflineConnector({ cache, readPolicy, mutationPolicy, outbox, replay, isOnline, clock = () => Date.now(), onCacheWriteError, }) {
21
+ async function readCached({ request, key, }) {
22
+ const cached = await cache.read({ key });
23
+ if (!cached || cached.expiresAt < clock()) {
24
+ throw new OfflineDataUnavailableError(request.path);
25
+ }
26
+ // Cache entries are written from this same typed request. The durable
27
+ // store cannot retain the erased generic, so restore it at the boundary.
28
+ return cached.response;
29
+ }
30
+ async function queueMutation({ request, descriptor, requestId, }) {
31
+ const observed = await descriptor.capture?.({ request });
32
+ const entry = await outbox.enqueue({
33
+ ...(requestId === undefined ? {} : { id: requestId }),
34
+ request,
35
+ entity: descriptor.entity,
36
+ ...(observed === undefined ? {} : { observed }),
37
+ });
38
+ await descriptor.optimisticApply?.({ entry });
39
+ const optimistic = descriptor.optimisticResponse?.({ entry }) ?? {
40
+ ok: true,
41
+ status: 202,
42
+ body: { offlineQueued: true, requestId: entry.id },
43
+ };
44
+ // Mutation descriptors define the response shape expected by their own
45
+ // call site; the shared queue deliberately treats payloads as unknown.
46
+ return optimistic;
47
+ }
48
+ const connector = {
49
+ async execute({ request, network }) {
50
+ if (request.method === 'GET') {
51
+ const descriptor = readPolicy.match(request);
52
+ if (!descriptor) {
53
+ if (!isOnline())
54
+ throw new OfflineDataUnavailableError(request.path);
55
+ return network(request);
56
+ }
57
+ if (!isOnline()) {
58
+ return readCached({ request, key: descriptor.key });
59
+ }
60
+ try {
61
+ const response = await network(request);
62
+ if (response.ok) {
63
+ const cachedAt = clock();
64
+ try {
65
+ await cache.write({
66
+ key: descriptor.key,
67
+ entry: {
68
+ response,
69
+ cachedAt,
70
+ expiresAt: cachedAt + descriptor.ttlMs,
71
+ entity: descriptor.entity,
72
+ },
73
+ });
74
+ }
75
+ catch (error) {
76
+ if (onCacheWriteError) {
77
+ onCacheWriteError({ request, error });
78
+ }
79
+ else {
80
+ console.warn('[artifact-studio] offline cache write failed:', error);
81
+ }
82
+ }
83
+ return response;
84
+ }
85
+ return canUseCachedFallback(response)
86
+ ? readCached({ request, key: descriptor.key })
87
+ : response;
88
+ }
89
+ catch {
90
+ return readCached({ request, key: descriptor.key });
91
+ }
92
+ }
93
+ let replayableRequest = request;
94
+ let requestId;
95
+ if (isOnline()) {
96
+ requestId = request.headers?.['X-Request-Id'] ?? globalThis.crypto.randomUUID();
97
+ replayableRequest = {
98
+ ...request,
99
+ headers: { ...request.headers, 'X-Request-Id': requestId },
100
+ };
101
+ try {
102
+ return await network(replayableRequest);
103
+ }
104
+ catch {
105
+ // A transport failure is an offline transition. Only the explicit
106
+ // mutation policy may decide whether this write is durable.
107
+ }
108
+ }
109
+ const descriptor = mutationPolicy.match(request);
110
+ if (!descriptor)
111
+ throw new OfflineWriteBlockedError(request.path);
112
+ return queueMutation({ request: replayableRequest, descriptor, requestId });
113
+ },
114
+ attach: ({ network }) => {
115
+ replay.attach({ network });
116
+ },
117
+ };
118
+ return {
119
+ connector,
120
+ flush: () => replay.flush(),
121
+ };
122
+ }
@@ -0,0 +1,5 @@
1
+ import type { OfflineCrypto } from './types.js';
2
+ export declare function generateOfflineEncryptionKey(): Promise<CryptoKey>;
3
+ export declare function createOfflineCrypto({ key }: {
4
+ key: CryptoKey;
5
+ }): OfflineCrypto;
@@ -0,0 +1,24 @@
1
+ const AES_GCM_IV_BYTES = 12;
2
+ export async function generateOfflineEncryptionKey() {
3
+ return globalThis.crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']);
4
+ }
5
+ export function createOfflineCrypto({ key }) {
6
+ return {
7
+ async encrypt({ value }) {
8
+ const serialized = JSON.stringify(value);
9
+ if (serialized === undefined) {
10
+ throw new Error('Offline values must be JSON-serializable');
11
+ }
12
+ const iv = globalThis.crypto.getRandomValues(new Uint8Array(AES_GCM_IV_BYTES));
13
+ const ciphertext = await globalThis.crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, new TextEncoder().encode(serialized));
14
+ return {
15
+ iv: Array.from(iv),
16
+ ciphertext: Array.from(new Uint8Array(ciphertext)),
17
+ };
18
+ },
19
+ async decrypt({ record }) {
20
+ const plaintext = await globalThis.crypto.subtle.decrypt({ name: 'AES-GCM', iv: new Uint8Array(record.iv) }, key, new Uint8Array(record.ciphertext));
21
+ return JSON.parse(new TextDecoder().decode(plaintext));
22
+ },
23
+ };
24
+ }
@@ -0,0 +1,8 @@
1
+ export { createArtifactOfflineConnector, OfflineDataUnavailableError, OfflineWriteBlockedError, type ArtifactOfflineConnector, } from './connector.js';
2
+ export { createOfflineCrypto, generateOfflineEncryptionKey, } from './crypto.js';
3
+ export { openOfflineIndexedDatabase, type OfflineIndexedDatabase, } from './indexed-db.js';
4
+ export { createOfflineCache } from './cache.js';
5
+ export { createOfflineOutbox } from './outbox.js';
6
+ export { classifyOfflineReplayFailure, createOfflineReplayEngine, type OfflineReplayEngine, } from './replay.js';
7
+ export { createEncryptedOfflineStore } from './secure-store.js';
8
+ export type { EncryptedOfflineStore, OfflineCache, OfflineCacheEntry, OfflineConflictField, OfflineConflictInfo, OfflineCrypto, OfflineEncryptedRecord, OfflineFailureKind, OfflineKvStore, OfflineMutationDescriptor, OfflineMutationPolicy, OfflineOutbox, OfflineOutboxEntry, OfflineReadDescriptor, OfflineReadPolicy, OfflineStoreName, OfflineUnresolvedWrite, } from './types.js';
@@ -0,0 +1,7 @@
1
+ export { createArtifactOfflineConnector, OfflineDataUnavailableError, OfflineWriteBlockedError, } from './connector.js';
2
+ export { createOfflineCrypto, generateOfflineEncryptionKey, } from './crypto.js';
3
+ export { openOfflineIndexedDatabase, } from './indexed-db.js';
4
+ export { createOfflineCache } from './cache.js';
5
+ export { createOfflineOutbox } from './outbox.js';
6
+ export { classifyOfflineReplayFailure, createOfflineReplayEngine, } from './replay.js';
7
+ export { createEncryptedOfflineStore } from './secure-store.js';
@@ -0,0 +1,10 @@
1
+ import type { OfflineKvStore } from './types.js';
2
+ export interface OfflineIndexedDatabase {
3
+ kv: OfflineKvStore;
4
+ nextSequence(): Promise<number>;
5
+ getOrCreateEncryptionKey(): Promise<CryptoKey>;
6
+ close(): void;
7
+ }
8
+ export declare function openOfflineIndexedDatabase({ databaseName, }: {
9
+ databaseName: string;
10
+ }): Promise<OfflineIndexedDatabase>;