@sequenceholdings/artifact-studio 0.3.0 → 0.3.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 (43) hide show
  1. package/VERSION-PIN.md +4 -0
  2. package/dist/cli.js +29 -3
  3. package/dist/gateway/admission.d.ts +2 -1
  4. package/dist/gateway/admission.js +3 -1
  5. package/dist/gateway/authorization-cache.d.ts +52 -0
  6. package/dist/gateway/authorization-cache.js +111 -0
  7. package/dist/gateway/control-admission.d.ts +48 -0
  8. package/dist/gateway/control-admission.js +127 -0
  9. package/dist/gateway/control.d.ts +4 -1
  10. package/dist/gateway/control.js +5 -2
  11. package/dist/gateway/hosted-proxy.js +4 -7
  12. package/dist/gateway/server.d.ts +9 -0
  13. package/dist/gateway/server.js +101 -17
  14. package/dist/gateway/session.d.ts +1 -0
  15. package/dist/gateway/session.js +2 -1
  16. package/dist/gateway/usage.d.ts +45 -12
  17. package/dist/gateway/usage.js +96 -26
  18. package/dist/gateway-main.js +35 -5
  19. package/dist/manifest-next.js +13 -1
  20. package/dist/next/bridge.js +8 -1
  21. package/dist/next-cache/config.js +31 -4
  22. package/dist/next-image-config.js +1 -1
  23. package/dist/server/client.d.ts +16 -2
  24. package/dist/server/request-context.d.ts +1 -0
  25. package/dist/server/request-context.js +4 -0
  26. package/dist/server/runtime-config.d.ts +1 -0
  27. package/dist/server.d.ts +1 -1
  28. package/dist/templates/nextjs/README.md +6 -0
  29. package/dist/templates/nextjs/gitignore +9 -0
  30. package/dist/templates/nextjs/pnpm-workspace.yaml +14 -0
  31. package/dist/trusted-install.js +62 -2
  32. package/dist/tunnel/broker.js +34 -22
  33. package/dist/tunnel/protocol.d.ts +9 -0
  34. package/dist/tunnel/protocol.js +31 -17
  35. package/dist/tunnel/redis-auth.d.ts +37 -0
  36. package/dist/tunnel/redis-auth.js +92 -0
  37. package/dist/tunnel/redis-types.d.ts +5 -0
  38. package/dist/tunnel/redis.d.ts +5 -2
  39. package/dist/tunnel/redis.js +19 -6
  40. package/package.json +1 -1
  41. package/templates/nextjs/README.md +6 -0
  42. package/templates/nextjs/gitignore +9 -0
  43. package/templates/nextjs/pnpm-workspace.yaml +14 -0
@@ -1,17 +1,20 @@
1
1
  import type { TunnelLease } from './broker.js';
2
2
  import type { TunnelRelay, TunnelSocket } from './transport.js';
3
- import { type RedisTunnelRelayOptions } from './redis-types.js';
3
+ import type { RedisTunnelRelayOptions } from './redis-types.js';
4
4
  export { RedisTunnelOwnership } from './redis-ownership.js';
5
5
  export type { RedisTunnelCommands } from './redis-ownership.js';
6
6
  export type { RedisTunnelPublisher, RedisTunnelSubscriber, RedisTunnelRelayOptions } from './redis-types.js';
7
7
  /** Internal Redis channels contain authorized application traffic. Deploy them
8
- * on the environment's private, authenticated Redis and disable command replay. */
8
+ * on the environment's private, authenticated Redis and disable command replay.
9
+ * Every message is authenticated with a gateway-only key: other Redis clients
10
+ * can neither inject, redirect nor replay tunnel frames. */
9
11
  export declare class RedisTunnelRelay implements TunnelRelay {
10
12
  private readonly options;
11
13
  private started;
12
14
  private closed;
13
15
  private readonly attached;
14
16
  private readonly connected;
17
+ private readonly frames;
15
18
  private publishing;
16
19
  private receiving;
17
20
  private outgoingBytes;
@@ -2,16 +2,19 @@ import { randomUUID } from 'node:crypto';
2
2
  import { DEFAULT_TUNNEL_LIMITS, parseTunnelFrame } from './protocol.js';
3
3
  import { redisIdentifier as identifier, redisNamespaceSchema as namespaceSchema, redisLeaseSchema as leaseSchema } from './redis-ownership.js';
4
4
  import { RelaySocket } from './redis-socket.js';
5
- import { envelopeSchema } from './redis-types.js';
5
+ import { deriveRelayFrameKey, RelayFrameAuthenticator } from './redis-auth.js';
6
6
  export { RedisTunnelOwnership } from './redis-ownership.js';
7
7
  /** Internal Redis channels contain authorized application traffic. Deploy them
8
- * on the environment's private, authenticated Redis and disable command replay. */
8
+ * on the environment's private, authenticated Redis and disable command replay.
9
+ * Every message is authenticated with a gateway-only key: other Redis clients
10
+ * can neither inject, redirect nor replay tunnel frames. */
9
11
  export class RedisTunnelRelay {
10
12
  options;
11
13
  started = false;
12
14
  closed = false;
13
15
  attached = new Map();
14
16
  connected = new Map();
17
+ frames;
15
18
  publishing = Promise.resolve();
16
19
  receiving = Promise.resolve();
17
20
  outgoingBytes = 0;
@@ -24,6 +27,7 @@ export class RedisTunnelRelay {
24
27
  namespaceSchema.parse(options.namespace);
25
28
  if (options.namespace !== options.ownership.namespace || Object.is(options.publisher, options.subscriber))
26
29
  throw new Error('Separate, environment-scoped Redis clients are required');
30
+ this.frames = new RelayFrameAuthenticator(deriveRelayFrameKey({ secret: options.secret, namespace: options.namespace }));
27
31
  if (options.connectTimeoutMs !== undefined && (!Number.isSafeInteger(options.connectTimeoutMs) || options.connectTimeoutMs < 1 || options.connectTimeoutMs > 60_000))
28
32
  throw new Error('Invalid tunnel connect timeout');
29
33
  const policy = options.publisher.options;
@@ -93,8 +97,15 @@ export class RedisTunnelRelay {
93
97
  onMessage = (channel, raw) => {
94
98
  if (!this.started || channel !== this.channel(this.options.nodeId))
95
99
  return;
100
+ // Authenticate before anything else: a message without a valid MAC, fresh timestamp and new
101
+ // sequence is dropped without resetting live traffic, so a Redis client cannot inject or DoS.
102
+ const envelope = this.frames.open({ channel, message: raw });
103
+ if (!envelope) {
104
+ this.options.onRejectedFrame?.();
105
+ return;
106
+ }
96
107
  const bytes = Buffer.byteLength(raw);
97
- if (bytes > DEFAULT_TUNNEL_LIMITS.maxFrameBytes * 2 + 2048 || this.incomingBytes + bytes > DEFAULT_TUNNEL_LIMITS.maxBufferedBytes) {
108
+ if (this.incomingBytes + bytes > DEFAULT_TUNNEL_LIMITS.maxBufferedBytes) {
98
109
  this.onRelayOverload();
99
110
  return;
100
111
  }
@@ -102,11 +113,13 @@ export class RedisTunnelRelay {
102
113
  const generation = this.generation;
103
114
  this.receiving = this.receiving.then(async () => {
104
115
  if (this.started && generation === this.generation)
105
- await this.receive(envelopeSchema.parse(JSON.parse(raw)));
116
+ await this.receive(envelope);
106
117
  }).catch(() => this.onRelayOverload()).finally(() => { this.incomingBytes -= bytes; });
107
118
  };
108
119
  publish(nodeId, envelope) {
109
- const payload = JSON.stringify(envelope);
120
+ const channel = this.channel(nodeId);
121
+ // Sealed in call order, so each destination sees this node's sequence numbers ascending.
122
+ const payload = this.frames.seal({ channel, envelope });
110
123
  const bytes = Buffer.byteLength(payload);
111
124
  if (this.outgoingBytes + bytes > DEFAULT_TUNNEL_LIMITS.maxBufferedBytes)
112
125
  return Promise.reject(new Error('Tunnel relay publish queue exceeded'));
@@ -115,7 +128,7 @@ export class RedisTunnelRelay {
115
128
  const sending = this.publishing.then(async () => {
116
129
  if (envelope.type !== 'close' && (generation !== this.generation || !this.started || this.closed))
117
130
  throw new Error('Tunnel relay generation changed');
118
- if (await this.options.publisher.publish(this.channel(nodeId), payload) < 1)
131
+ if (await this.options.publisher.publish(channel, payload) < 1)
119
132
  throw new Error('Tunnel node is offline');
120
133
  });
121
134
  this.publishing = sending.catch(() => undefined).finally(() => { this.outgoingBytes -= bytes; });
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "publishConfig": {
10
10
  "access": "public"
11
11
  },
12
- "version": "0.3.0",
12
+ "version": "0.3.1",
13
13
  "description": "SDK types and CLI library for building Artifact Studio apps. Driven via `seq-studio artifact <sub>` (@sequenceholdings/studio-cli), which runs the `./cli` runCli export.",
14
14
  "type": "module",
15
15
  "exports": {
@@ -71,6 +71,12 @@ only the capabilities the application needs, then configure authorized resource
71
71
  bindings in Atlas. Local development uses explicit nonproduction resources and
72
72
  never falls back to production when a binding is missing.
73
73
 
74
+ ORM access is declared per table and per mutation: list `<namespace>/<table>`
75
+ under `data.read` for every table a query reads (queries that follow
76
+ platform-user relations also read `<namespace>/platformUsers`). Under
77
+ `data.write`, use `<namespace>/ops/<Operation>` for each persisted mutation the
78
+ app runs, or `<namespace>/<table>` for table-level coverage.
79
+
74
80
  Managed secrets stay in hosted runtimes. For local integration tests, use your
75
81
  own test credentials, mocks or an existing governed function. Use a hosted
76
82
  preview for application code that needs a managed secret. Never put secrets in
@@ -0,0 +1,9 @@
1
+ node_modules/
2
+ .next/
3
+ next-env.d.ts
4
+ .artifact/
5
+ .artifact-studio/
6
+ .env*
7
+ *.tsbuildinfo
8
+ coverage/
9
+ .DS_Store
@@ -2,7 +2,21 @@ packages:
2
2
  - '.'
3
3
 
4
4
  minimumReleaseAge: 10080
5
+ # The Sequence SDK chain is published from the Studio repo's audited release
6
+ # pipeline, so a new artifact-studio release must install without waiting out
7
+ # the seven-day quarantine. Scoped to the exact first-party packages a fresh
8
+ # scaffold resolves, not @sequenceholdings/*.
9
+ minimumReleaseAgeExclude:
10
+ - '@sequenceholdings/artifact-studio'
11
+ - '@sequenceholdings/atlas-ui'
12
+ - '@sequenceholdings/custom-roles-manifest'
13
+ - '@sequenceholdings/lattice-form-renderer'
14
+ - '@sequenceholdings/orm'
15
+ - '@sequenceholdings/storage'
5
16
  strictDepBuilds: true
6
17
  allowBuilds:
7
18
  '@tailwindcss/oxide': false
19
+ # Reached through the SDK's static builder and ORM tooling, never run by a
20
+ # Next.js app; its platform binary installs as an optional dependency.
21
+ esbuild: false
8
22
  sharp: false