@supacloud/cli 0.96.1 → 0.97.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -10925,12 +10925,157 @@ function parseDeploymentEvidence(value) {
10925
10925
  }
10926
10926
  return structuredClone(value);
10927
10927
  }
10928
- // node_modules/@supacloud/delivery/src/application-promotion.ts
10928
+ // node_modules/@supacloud/delivery/src/delivery-tuple.ts
10929
+ var sha2563 = String2({ pattern: "^[a-f0-9]{64}$" });
10930
+ var commit = String2({ pattern: "^(?:[a-f0-9]{40}|[a-f0-9]{64})$" });
10931
+ var isoDate2 = String2({ minLength: 20, maxLength: 40 });
10932
+ var DELIVERY_TUPLE_SCHEMA = "supacloud.fa-delivery-tuple.v1";
10933
+ var FaDeliveryTupleSchema = _Object_({
10934
+ schema: Literal(DELIVERY_TUPLE_SCHEMA),
10935
+ customer: Literal("xigu-fa"),
10936
+ fa_commit: commit,
10937
+ cli: _Object_({
10938
+ version: String2({ pattern: "^\\d+\\.\\d+\\.\\d+$" })
10939
+ }, { additionalProperties: false }),
10940
+ platform_commit: commit,
10941
+ edge_runtime: _Object_({
10942
+ version: String2({ minLength: 1, maxLength: 128 }),
10943
+ artifact_sha256: sha2563
10944
+ }, { additionalProperties: false }),
10945
+ compatibility_window: _Object_({
10946
+ contract_schema: String2({ minLength: 1, maxLength: 128 }),
10947
+ starts_at: isoDate2,
10948
+ ends_at: isoDate2,
10949
+ old_client_supported: Boolean2(),
10950
+ partial_deployment_supported: Boolean2()
10951
+ }, { additionalProperties: false })
10952
+ }, { additionalProperties: false });
10953
+ // node_modules/@supacloud/delivery/src/contract-matrix.ts
10929
10954
  var closed6 = { additionalProperties: false };
10955
+ var sha2564 = String2({ pattern: "^[a-f0-9]{64}$" });
10956
+ var generation = Union([Literal("old"), Literal("new")]);
10957
+ var versions = _Object_({ old: sha2564, new: sha2564 }, closed6);
10958
+ var CONTRACT_MATRIX_SCHEMA = "supacloud.consumer-contract-matrix.v1";
10959
+ var FA_CONTRACT_SCENARIOS = [
10960
+ "missing-display-field",
10961
+ "missing-authority-field",
10962
+ "added-field",
10963
+ "legacy-envelope",
10964
+ "lost-write-response",
10965
+ "downgrade"
10966
+ ];
10967
+ var ConsumerContractMatrixSchema = _Object_({
10968
+ schema: Literal(CONTRACT_MATRIX_SCHEMA),
10969
+ customer: Literal("xigu-fa"),
10970
+ baseline_tuple_sha256: sha2564,
10971
+ candidate_tuple_sha256: sha2564,
10972
+ artifacts: _Object_({ frontend: versions, functions: versions, database: versions }, closed6),
10973
+ cases: _Array_(_Object_({
10974
+ frontend: generation,
10975
+ functions: generation,
10976
+ database: generation,
10977
+ scenarios: _Array_(_Object_({
10978
+ name: Enum(FA_CONTRACT_SCENARIOS),
10979
+ expected: Union([Literal("compatible"), Literal("blocked-safely")])
10980
+ }, closed6), { minItems: FA_CONTRACT_SCENARIOS.length, maxItems: FA_CONTRACT_SCENARIOS.length })
10981
+ }, closed6), { minItems: 8, maxItems: 8 })
10982
+ }, closed6);
10983
+ // node_modules/@supacloud/delivery/src/fa-stability.ts
10984
+ var closed7 = { additionalProperties: false };
10985
+ var hash3 = String2({ pattern: "^[a-f0-9]{64}$" });
10986
+ var label = String2({ minLength: 1, maxLength: 256, pattern: "\\S" });
10987
+ var timestamp2 = String2({ minLength: 24, maxLength: 24 });
10988
+ var count = Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER });
10989
+ var environment2 = Union([Literal("test"), Literal("production")]);
10990
+ var FA_REQUIRED_ACCEPTANCE = {
10991
+ login: "real-browser",
10992
+ permissions: "real-http",
10993
+ upload: "real-http",
10994
+ "async-task": "real-worker",
10995
+ deploy: "real-http",
10996
+ "database-migration": "real-database",
10997
+ schedule: "real-worker",
10998
+ rollback: "real-drill",
10999
+ "error-policy": "real-http",
11000
+ "alert-delivery": "real-drill"
11001
+ };
11002
+ var FA_BUSINESS_OPERATIONS = ["login", "permissions", "upload", "async-task", "report", "deploy"];
11003
+ var operation = Enum(FA_BUSINESS_OPERATIONS);
11004
+ var proof = _Object_({
11005
+ path: String2({ pattern: "^[A-Za-z0-9_-][A-Za-z0-9_./-]{0,255}$" }),
11006
+ sha256: hash3
11007
+ }, closed7);
11008
+ var FaBusinessObservationSchema = _Object_({
11009
+ id: String2({ pattern: "^[A-Za-z0-9_-]{1,128}$" }),
11010
+ tuple_sha256: hash3,
11011
+ operation,
11012
+ started_at: timestamp2,
11013
+ finished_at: timestamp2,
11014
+ outcome: Enum(["success", "failure", "timeout", "unknown"]),
11015
+ attempts: Integer({ minimum: 1, maximum: 1000 }),
11016
+ rollbacks: count
11017
+ }, closed7);
11018
+ var FaServiceObjectivesSchema = _Object_({
11019
+ status: Literal("approved"),
11020
+ owner: label,
11021
+ approval_reference: label,
11022
+ approved_at: timestamp2,
11023
+ project_ref: String2({ pattern: "^[a-z0-9-]{1,20}$" }),
11024
+ environment: environment2,
11025
+ alert_route: label,
11026
+ window_hours: Number2({ exclusiveMinimum: 0, maximum: 8760 }),
11027
+ max_evidence_age_hours: Number2({ exclusiveMinimum: 0, maximum: 168 }),
11028
+ operations: _Array_(_Object_({
11029
+ operation,
11030
+ minimum_samples: Integer({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER }),
11031
+ availability_percent: Number2({ exclusiveMinimum: 0, maximum: 100 }),
11032
+ p95_ms: Number2({ exclusiveMinimum: 0, maximum: Number.MAX_SAFE_INTEGER })
11033
+ }, closed7), { minItems: FA_BUSINESS_OPERATIONS.length, maxItems: FA_BUSINESS_OPERATIONS.length })
11034
+ }, closed7);
11035
+ var FaStabilityEvidenceSchema = _Object_({
11036
+ schema: Literal("supacloud.fa-stability-evidence.v1"),
11037
+ environment: environment2,
11038
+ project_ref: String2({ pattern: "^[a-z0-9-]{1,20}$" }),
11039
+ tuple_sha256: hash3,
11040
+ matrix_sha256: hash3,
11041
+ objectives_sha256: hash3,
11042
+ recorded_at: timestamp2,
11043
+ window_start: timestamp2,
11044
+ window_end: timestamp2,
11045
+ reviewed_by: label,
11046
+ review_reference: label,
11047
+ acceptance: _Array_(_Object_({
11048
+ id: Enum(Object.keys(FA_REQUIRED_ACCEPTANCE)),
11049
+ execution: Enum([
11050
+ "real-browser",
11051
+ "real-http",
11052
+ "real-worker",
11053
+ "real-database",
11054
+ "real-drill",
11055
+ "local-fake"
11056
+ ]),
11057
+ passed: Boolean2(),
11058
+ measured_at: timestamp2,
11059
+ proof
11060
+ }, closed7), { minItems: 10, maxItems: 10 }),
11061
+ matrix: _Array_(_Object_({
11062
+ combination: label,
11063
+ scenario: label,
11064
+ outcome: Union([Literal("compatible"), Literal("blocked-safely")]),
11065
+ execution: Union([Literal("real-http-and-database"), Literal("local-fake")]),
11066
+ passed: Boolean2(),
11067
+ measured_at: timestamp2,
11068
+ proof
11069
+ }, closed7), { minItems: 48, maxItems: 48 }),
11070
+ observation: proof,
11071
+ alert: _Object_({ route: label, acknowledged_by: label, delivered_at: timestamp2 }, closed7)
11072
+ }, closed7);
11073
+ // node_modules/@supacloud/delivery/src/application-promotion.ts
11074
+ var closed8 = { additionalProperties: false };
10930
11075
  var projectRef2 = String2({ pattern: "^[a-z0-9-]{1,20}$" });
10931
- var sha2563 = ApplicationReleaseIdSchema;
11076
+ var sha2565 = ApplicationReleaseIdSchema;
10932
11077
  var nullableActivation2 = Union([ApplicationActivationIdSchema, Null()]);
10933
- var nullableDigest = Union([sha2563, Null()]);
11078
+ var nullableDigest = Union([sha2565, Null()]);
10934
11079
  var observation = {
10935
11080
  activation_id: nullableActivation2,
10936
11081
  receipt_confirmed: Boolean2(),
@@ -10956,33 +11101,33 @@ var ApplicationPromotionPlanSchema = _Object_({
10956
11101
  project_ref: projectRef2,
10957
11102
  application_id: ApplicationIdSchema,
10958
11103
  environment_id: ApplicationIdSchema,
10959
- manifest_sha256: sha2563,
10960
- plan_sha256: sha2563,
11104
+ manifest_sha256: sha2565,
11105
+ plan_sha256: sha2565,
10961
11106
  source: _Object_({
10962
11107
  project_ref: projectRef2,
10963
11108
  environment_id: ApplicationIdSchema,
10964
- release_id: sha2563,
11109
+ release_id: sha2565,
10965
11110
  ...observation
10966
- }, closed6),
11111
+ }, closed8),
10967
11112
  target: _Object_({
10968
- candidate_release_id: sha2563,
10969
- current_release_id: Union([sha2563, Null()]),
11113
+ candidate_release_id: sha2565,
11114
+ current_release_id: Union([sha2565, Null()]),
10970
11115
  artifact_action: Union([Literal("materialize"), Literal("reuse")]),
10971
11116
  configuration_id: Union([ApplicationConfigurationIdSchema, Null()]),
10972
11117
  configuration: Union([ApplicationConfigurationViewSchema, Null()]),
10973
11118
  ...observation
10974
- }, closed6),
11119
+ }, closed8),
10975
11120
  migrations: _Object_({
10976
- ledger_digest: sha2563,
11121
+ ledger_digest: sha2565,
10977
11122
  ledger_compatible: Boolean2(),
10978
11123
  project_migrations_applied: Boolean2(),
10979
11124
  pending_versions: _Array_(String2({ pattern: "^\\d{1,20}$" }), { maxItems: 4096, uniqueItems: true }),
10980
11125
  operator_provisioning_required: Boolean2()
10981
- }, closed6),
11126
+ }, closed8),
10982
11127
  backup: _Object_({
10983
11128
  required: Boolean2(),
10984
11129
  confirmed: Literal(false)
10985
- }, closed6),
11130
+ }, closed8),
10986
11131
  action: Union([Literal("promote"), Literal("no-op"), Literal("blocked")]),
10987
11132
  blockers: _Array_(Enum([...blocker]), {
10988
11133
  maxItems: blocker.length,
@@ -10991,7 +11136,7 @@ var ApplicationPromotionPlanSchema = _Object_({
10991
11136
  steps: _Array_(String2({ minLength: 1, maxLength: 64 }), { maxItems: 8, uniqueItems: true }),
10992
11137
  execution_performed: Literal(false),
10993
11138
  data_recovery: Literal("separate-required")
10994
- }, closed6);
11139
+ }, closed8);
10995
11140
  function applicationPromotionPlanDigest(value) {
10996
11141
  return digest(canonical(value));
10997
11142
  }
@@ -17191,12 +17336,12 @@ async function fetchMutationStatus(http, ref, mutationId) {
17191
17336
  }
17192
17337
 
17193
17338
  // src/shared/tools/application-workflow-schemas.ts
17194
- var closed7 = { additionalProperties: false };
17339
+ var closed9 = { additionalProperties: false };
17195
17340
  var ref5 = String2({ pattern: "^[A-Za-z0-9_-]{1,20}$" });
17196
17341
  var uuid = ApplicationActivationIdSchema2;
17197
17342
  var nullableUuid = Union([uuid, Null()]);
17198
17343
  var nullableDigest2 = Union([ApplicationReleaseIdSchema2, Null()]);
17199
- var timestamp2 = String2({ pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$" });
17344
+ var timestamp3 = String2({ pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$" });
17200
17345
  var scope2 = { project_ref: ref5, application_id: ApplicationIdSchema2, environment_id: ApplicationIdSchema2 };
17201
17346
  var ApplicationPromotionResultSchema = _Object_({
17202
17347
  ...scope2,
@@ -17205,7 +17350,7 @@ var ApplicationPromotionResultSchema = _Object_({
17205
17350
  plan_sha256: ApplicationReleaseIdSchema2,
17206
17351
  action: Literal("promote"),
17207
17352
  replayed: Boolean2()
17208
- }, closed7);
17353
+ }, closed9);
17209
17354
  var ApplicationPromotionStatusSchema = _Object_({
17210
17355
  ...scope2,
17211
17356
  mutation: Unknown(),
@@ -17224,8 +17369,8 @@ var ApplicationPromotionStatusSchema = _Object_({
17224
17369
  configuration_id: Union([ApplicationConfigurationIdSchema2, Null()]),
17225
17370
  backup_id: Union([String2({ pattern: "^logical-full_[a-z0-9-]{1,20}_[a-f0-9]{32}$" }), Null()]),
17226
17371
  data_recovery: Literal("separate-required")
17227
- }, closed7)
17228
- }, closed7);
17372
+ }, closed9)
17373
+ }, closed9);
17229
17374
  var phase = Union([Literal("pending"), Literal("ready"), Literal("failed"), Literal("cleaned")]);
17230
17375
  var previewId = String2({ pattern: "^[a-f0-9-]{8,64}$" });
17231
17376
  var name = String2({ minLength: 1, maxLength: 256 });
@@ -17235,7 +17380,7 @@ var ApplicationPreviewReceiptSchema2 = _Object_({
17235
17380
  ...scope2,
17236
17381
  preview_id: previewId,
17237
17382
  release_id: ApplicationReleaseIdSchema2,
17238
- expires_at: Union([timestamp2, Null()]),
17383
+ expires_at: Union([timestamp3, Null()]),
17239
17384
  status: Union([
17240
17385
  Literal("planned"),
17241
17386
  Literal("provisioning"),
@@ -17244,35 +17389,35 @@ var ApplicationPreviewReceiptSchema2 = _Object_({
17244
17389
  Literal("cleaned")
17245
17390
  ]),
17246
17391
  resources: _Object_({
17247
- build_artifact: _Object_({ status: phase, release_id: ApplicationReleaseIdSchema2 }, closed7),
17392
+ build_artifact: _Object_({ status: phase, release_id: ApplicationReleaseIdSchema2 }, closed9),
17248
17393
  database_branch: _Object_({
17249
17394
  status: phase,
17250
17395
  branch_ref: ref5,
17251
17396
  data_mode: Union([Literal("schema_only"), Literal("full_clone")])
17252
- }, closed7),
17253
- queue_namespace: _Object_({ status: phase, namespace: name }, closed7),
17254
- storage_namespace: _Object_({ status: phase, namespace: name }, closed7),
17255
- test_secret: _Object_({ status: phase, name, value_issued: Literal(false) }, closed7),
17256
- configuration_revision: _Object_({ status: phase, configuration_id: Union([ApplicationConfigurationIdSchema2, Null()]) }, closed7),
17257
- application_activation: _Object_({ status: phase, activation_id: nullableUuid }, closed7),
17258
- smoke_test: _Object_({ status: phase, checks, passed: checks, failed: checks }, closed7)
17259
- }, closed7),
17397
+ }, closed9),
17398
+ queue_namespace: _Object_({ status: phase, namespace: name }, closed9),
17399
+ storage_namespace: _Object_({ status: phase, namespace: name }, closed9),
17400
+ test_secret: _Object_({ status: phase, name, value_issued: Literal(false) }, closed9),
17401
+ configuration_revision: _Object_({ status: phase, configuration_id: Union([ApplicationConfigurationIdSchema2, Null()]) }, closed9),
17402
+ application_activation: _Object_({ status: phase, activation_id: nullableUuid }, closed9),
17403
+ smoke_test: _Object_({ status: phase, checks, passed: checks, failed: checks }, closed9)
17404
+ }, closed9),
17260
17405
  cleanup: _Object_({
17261
17406
  required: Boolean2(),
17262
17407
  completed: Boolean2(),
17263
17408
  error: Union([String2({ pattern: "^[A-Z][A-Z0-9_]{0,63}$" }), Null()])
17264
- }, closed7),
17409
+ }, closed9),
17265
17410
  branch_name: Optional(String2({ minLength: 1, maxLength: 80 })),
17266
17411
  queue_name: Optional(name),
17267
17412
  test_secret_name: Optional(name),
17268
17413
  source_configuration_id: Optional(Union([ApplicationConfigurationIdSchema2, Null()])),
17269
- created_at: Optional(timestamp2),
17270
- updated_at: Optional(timestamp2)
17271
- }, closed7);
17414
+ created_at: Optional(timestamp3),
17415
+ updated_at: Optional(timestamp3)
17416
+ }, closed9);
17272
17417
  var previewListSchema = _Object_({
17273
17418
  ...scope2,
17274
17419
  previews: _Array_(ApplicationPreviewReceiptSchema2, { maxItems: 1000 })
17275
- }, closed7);
17420
+ }, closed9);
17276
17421
  function assertScope(value, identity) {
17277
17422
  if (value.project_ref !== identity.project_ref || value.application_id !== identity.application_id || value.environment_id !== identity.environment_id)
17278
17423
  throw new Error("Application workflow scope mismatch");
@@ -17466,14 +17611,14 @@ var actionFields = {
17466
17611
  reconcile_preview: ["preview_id"],
17467
17612
  cleanup_preview: ["preview_id"]
17468
17613
  };
17469
- function timestamp3(value) {
17614
+ function timestamp4(value) {
17470
17615
  return Number.isFinite(Date.parse(value)) && new Date(value).toISOString() === value;
17471
17616
  }
17472
17617
  function receipt(value, identity) {
17473
17618
  if (!Check2(receiptSchema, value) || value.project_ref !== identity.ref || value.application_id !== identity.id || value.environment_id !== identity.environment || identity.previewId && value.preview_id !== identity.previewId || value.resources.build_artifact.release_id !== value.release_id || value.resources.storage_namespace.namespace !== value.resources.database_branch.branch_ref) {
17474
17619
  throw new Error("Invalid preview receipt");
17475
17620
  }
17476
- if (value.expires_at !== undefined && value.expires_at !== null && !timestamp3(value.expires_at) || value.created_at !== undefined && !timestamp3(value.created_at) || value.updated_at !== undefined && !timestamp3(value.updated_at) || value.expires_at !== undefined && value.expires_at !== null && value.created_at !== undefined && !Check2(ttlSecondsSchema, (Date.parse(value.expires_at) - Date.parse(value.created_at)) / 1000)) {
17621
+ if (value.expires_at !== undefined && value.expires_at !== null && !timestamp4(value.expires_at) || value.created_at !== undefined && !timestamp4(value.created_at) || value.updated_at !== undefined && !timestamp4(value.updated_at) || value.expires_at !== undefined && value.expires_at !== null && value.created_at !== undefined && !Check2(ttlSecondsSchema, (Date.parse(value.expires_at) - Date.parse(value.created_at)) / 1000)) {
17477
17622
  throw new Error("Invalid preview expiry");
17478
17623
  }
17479
17624
  if (value.status === "ready") {
@@ -21200,22 +21345,22 @@ var starter_metadata_default = {
21200
21345
  generatedBy: "supacloud/starter-metadata-v1",
21201
21346
  packages: {
21202
21347
  compiler: {
21203
- version: "0.46.0"
21348
+ version: "0.46.1"
21204
21349
  },
21205
21350
  app: {
21206
- version: "0.26.1",
21351
+ version: "0.27.0",
21207
21352
  dependencies: {
21208
21353
  rxjs: "7.8.2"
21209
21354
  }
21210
21355
  },
21211
21356
  elysia: {
21212
- version: "0.29.2"
21357
+ version: "0.30.0"
21213
21358
  },
21214
21359
  commands: {
21215
21360
  version: "0.9.2"
21216
21361
  },
21217
21362
  contracts: {
21218
- version: "0.8.1"
21363
+ version: "0.9.0"
21219
21364
  },
21220
21365
  db: {
21221
21366
  version: "0.17.0",
@@ -24242,6 +24387,302 @@ test("${name} controller preserves an asynchronous read failure", async () => {
24242
24387
  };
24243
24388
  }
24244
24389
 
24390
+ // src/shared/tools/app-feature.ts
24391
+ function featureScaffold(name, className, governance) {
24392
+ const commandName = `${name}.approve`;
24393
+ const { permission, rpc } = governance;
24394
+ return {
24395
+ [`${name}.contracts.ts`]: `import { t } from "elysia";
24396
+
24397
+ export const ${className}Params = t.Object({
24398
+ id: t.String({ minLength: 1, maxLength: 128, pattern: "^[A-Za-z0-9_-]+$" }),
24399
+ });
24400
+ export const ${className}ListQuery = t.Object({
24401
+ state: t.Optional(t.String({ minLength: 1, maxLength: 64 })),
24402
+ });
24403
+ export const ${className}ApproveBody = t.Object({
24404
+ expectedVersion: t.Integer({ minimum: 1 }),
24405
+ reason: t.Optional(t.String({ maxLength: 2000 })),
24406
+ });
24407
+ export const ${className}Row = t.Object({
24408
+ id: t.String(),
24409
+ state: t.String(),
24410
+ version: t.Integer({ minimum: 1 }),
24411
+ });
24412
+ export const ${className}ListResponse = t.Array(${className}Row);
24413
+ `,
24414
+ [`${name}.model.ts`]: `import { InjectionToken } from "@supacloud/app/core";
24415
+ import type {
24416
+ ${className}ApproveBody,
24417
+ ${className}ListQuery,
24418
+ ${className}Row,
24419
+ } from "./${name}.contracts";
24420
+ import type { StaticDecode } from "typebox";
24421
+
24422
+ export type ${className}ListInput = StaticDecode<typeof ${className}ListQuery>;
24423
+ export type ${className}ApproveInput = StaticDecode<typeof ${className}ApproveBody>;
24424
+ export type ${className}Result = StaticDecode<typeof ${className}Row>;
24425
+
24426
+ export interface ${className}ReadPort {
24427
+ list(input: ${className}ListInput): Promise<${className}Result[]>;
24428
+ detail(id: string): Promise<${className}Result>;
24429
+ }
24430
+
24431
+ export interface ${className}WritePort {
24432
+ approve(input: {
24433
+ id: string;
24434
+ expectedVersion: number;
24435
+ reason?: string;
24436
+ }): Promise<${className}Result>;
24437
+ }
24438
+
24439
+ export const ${className}Reader = new InjectionToken<${className}ReadPort>(
24440
+ "${name}.reader", { scope: "request" },
24441
+ );
24442
+ export const ${className}Writer = new InjectionToken<${className}WritePort>(
24443
+ "${name}.writer", { scope: "request" },
24444
+ );
24445
+ `,
24446
+ [`${name}.service.ts`]: `import { Inject, Injectable, Optional } from "@supacloud/app/core";
24447
+ import {
24448
+ ${className}Reader,
24449
+ ${className}Writer,
24450
+ type ${className}ApproveInput,
24451
+ type ${className}ReadPort,
24452
+ type ${className}Result,
24453
+ type ${className}WritePort,
24454
+ } from "./${name}.model";
24455
+
24456
+ @Injectable({ scope: "request" })
24457
+ export class ${className}Service {
24458
+ constructor(
24459
+ @Inject(${className}Reader) @Optional() private readonly reader?: ${className}ReadPort,
24460
+ @Inject(${className}Writer) @Optional() private readonly writer?: ${className}WritePort,
24461
+ ) {}
24462
+
24463
+ async list(input: Parameters<${className}ReadPort["list"]>[0]): Promise<${className}Result[]> {
24464
+ if (!this.reader) throw new Error("Bind ${className}Reader with verified identity and tenant scope");
24465
+ return this.reader.list(input);
24466
+ }
24467
+
24468
+ async detail(id: string): Promise<${className}Result> {
24469
+ if (!this.reader) throw new Error("Bind ${className}Reader with verified identity and tenant scope");
24470
+ return this.reader.detail(id);
24471
+ }
24472
+
24473
+ async approve(id: string, input: ${className}ApproveInput): Promise<${className}Result> {
24474
+ if (!this.writer) throw new Error("Bind ${className}Writer with durable command governance");
24475
+ return this.writer.approve({ ...input, id });
24476
+ }
24477
+ }
24478
+ `,
24479
+ [`${name}.feature.ts`]: `import { Effect } from "effect";
24480
+ import {
24481
+ Body, Command, Controller, defineFeatureSlice, defineFeatureSpec, Get, Inject,
24482
+ Injectable, Job, Param, Post, Query, type FeatureState,
24483
+ } from "@supacloud/app/core";
24484
+ import {
24485
+ ${className}ApproveBody,
24486
+ ${className}ListQuery,
24487
+ ${className}ListResponse,
24488
+ ${className}Params,
24489
+ ${className}Row,
24490
+ } from "./${name}.contracts";
24491
+ import { ${className}Service } from "./${name}.service";
24492
+ import type { ${className}ApproveInput, ${className}ListInput, ${className}Result } from "./${name}.model";
24493
+
24494
+ export const featureSpec = defineFeatureSpec({
24495
+ name: ${JSON.stringify(name)},
24496
+ states: ["draft", "approved"],
24497
+ transitions: {
24498
+ approve: {
24499
+ from: "draft", to: "approved", command: "Approve${className}Command",
24500
+ permission: ${JSON.stringify(permission)}, transaction: "required",
24501
+ idempotency: "required", audit: ${JSON.stringify(`${name}.approved`)},
24502
+ route: "POST /${name}/:id/approve",
24503
+ },
24504
+ },
24505
+ });
24506
+ export type ${className}State = FeatureState<typeof featureSpec>;
24507
+
24508
+ @Injectable({ scope: "request" })
24509
+ @Query({ name: "${name}.list" })
24510
+ export class List${className}Query {
24511
+ constructor(@Inject(${className}Service) private readonly service: ${className}Service) {}
24512
+ execute(input: ${className}ListInput): Promise<${className}Result[]> {
24513
+ return this.service.list(input);
24514
+ }
24515
+ }
24516
+
24517
+ @Injectable({ scope: "request" })
24518
+ @Command({
24519
+ name: ${JSON.stringify(commandName)},
24520
+ permission: ${JSON.stringify(permission)},
24521
+ transaction: "required",
24522
+ audit: ${JSON.stringify(`${name}.approved`)},
24523
+ idempotency: "required",
24524
+ rpc: ${JSON.stringify(rpc)},
24525
+ })
24526
+ export class Approve${className}Command {
24527
+ constructor(@Inject(${className}Service) private readonly service: ${className}Service) {}
24528
+ execute(id: string, input: ${className}ApproveInput): Promise<${className}Result> {
24529
+ return this.service.approve(id, input);
24530
+ }
24531
+ }
24532
+
24533
+ @Job({
24534
+ name: ${JSON.stringify(`${name}.refresh`)},
24535
+ mode: "task",
24536
+ idempotency: "required",
24537
+ })
24538
+ export class Refresh${className}Job {
24539
+ run(_input: { id: string; version: number }): Promise<void> {
24540
+ throw new Error("Bind the existing Workflow/Queue adapter before dispatching ${name}.refresh");
24541
+ }
24542
+ }
24543
+
24544
+ @Controller("/${name}")
24545
+ export class ${className}Controller {
24546
+ constructor(
24547
+ @Inject(List${className}Query) private readonly listQuery: List${className}Query,
24548
+ @Inject(Approve${className}Command) private readonly approveCommand: Approve${className}Command,
24549
+ @Inject(${className}Service) private readonly service: ${className}Service,
24550
+ ) {}
24551
+
24552
+ @Get("/", {
24553
+ query: ${className}ListQuery,
24554
+ responses: { 200: ${className}ListResponse },
24555
+ effect: { required: true, dependencies: [], errors: [], retry: "none" },
24556
+ })
24557
+ list(@Query("state") state?: string): Effect.Effect<${className}Result[], never, never> {
24558
+ return Effect.promise(() => this.listQuery.execute(state === undefined ? {} : { state }));
24559
+ }
24560
+
24561
+ @Get("/:id", {
24562
+ params: ${className}Params,
24563
+ responses: { 200: ${className}Row },
24564
+ effect: { required: true, dependencies: [], errors: [], retry: "none" },
24565
+ })
24566
+ detail(@Param("id") id: string): Effect.Effect<${className}Result, never, never> {
24567
+ return Effect.promise(() => this.service.detail(id));
24568
+ }
24569
+
24570
+ @Post("/:id/approve", {
24571
+ params: ${className}Params,
24572
+ body: ${className}ApproveBody,
24573
+ responses: { 200: ${className}Row },
24574
+ command: Approve${className}Command,
24575
+ effect: { required: true, dependencies: [], errors: [], retry: "none" },
24576
+ })
24577
+ approve(
24578
+ @Param("id") id: string,
24579
+ @Body() body: ${className}ApproveInput,
24580
+ ): Effect.Effect<${className}Result, never, never> {
24581
+ return Effect.promise(() => this.approveCommand.execute(id, body));
24582
+ }
24583
+ }
24584
+
24585
+ export const ${className}Feature = defineFeatureSlice({
24586
+ name: ${JSON.stringify(name)},
24587
+ tags: ["type:feature"],
24588
+ spec: featureSpec,
24589
+ providers: [${className}Service, List${className}Query, Approve${className}Command],
24590
+ controllers: [${className}Controller],
24591
+ jobs: [Refresh${className}Job],
24592
+ });
24593
+ `,
24594
+ [`${name}.feature.test.ts`]: `import { expect, test } from "bun:test";
24595
+ import { ${className}Service } from "./${name}.service";
24596
+
24597
+ test("${name} fails closed until verified read and durable write ports are bound", async () => {
24598
+ const service = new ${className}Service();
24599
+ await expect(service.list({})).rejects.toThrow("Bind ${className}Reader");
24600
+ await expect(service.detail("example")).rejects.toThrow("Bind ${className}Reader");
24601
+ await expect(service.approve("example", { expectedVersion: 1 })).rejects.toThrow("Bind ${className}Writer");
24602
+ });
24603
+
24604
+ test("${name} preserves denied reads and rejected writes without fallback or retry", async () => {
24605
+ const denied = new Error("denied");
24606
+ let attempts = 0;
24607
+ const service = new ${className}Service({
24608
+ list: async () => { throw denied; },
24609
+ detail: async () => { throw denied; },
24610
+ }, {
24611
+ approve: async () => { attempts++; throw denied; },
24612
+ });
24613
+ await expect(service.list({})).rejects.toBe(denied);
24614
+ await expect(service.detail("example")).rejects.toBe(denied);
24615
+ await expect(service.approve("example", { expectedVersion: 1 })).rejects.toBe(denied);
24616
+ expect(attempts).toBe(1);
24617
+ });
24618
+
24619
+ test("${name} forwards the authoritative version and result without inventing persistence", async () => {
24620
+ const row = { id: "example", state: "approved", version: 2 };
24621
+ const service = new ${className}Service(undefined, {
24622
+ async approve(input) {
24623
+ expect(input).toEqual({ id: "example", expectedVersion: 1, reason: "reviewed" });
24624
+ return row;
24625
+ },
24626
+ });
24627
+ expect(await service.approve("example", { expectedVersion: 1, reason: "reviewed" })).toBe(row);
24628
+ });
24629
+ `,
24630
+ [`${name}.deployment.md`]: `# ${className} feature delivery checklist
24631
+
24632
+ - [ ] Bind ${className}Reader to a tenant-scoped, authenticated database read path.
24633
+ - [ ] Bind ${className}Writer to a transactional RPC or persistent Command adapter.
24634
+ - [ ] Register the \`${rpc}\` compiler and runtime RPC capability.
24635
+ - [ ] Verify RLS, permission denial, stale version, identical replay and conflicting replay.
24636
+ - [ ] Verify the refresh job uses the existing Workflow/Queue delivery path.
24637
+ - [ ] Run the generated client contract and authenticated E2E test.
24638
+ - [ ] Run \`supacloud app check --format json\`, package tests and the selected deployment receipt.
24639
+
24640
+ The scaffold does not create migrations, infer permissions, or provide an in-memory
24641
+ production fallback. Database migrations remain the database source of truth.
24642
+ The HTTP schemas describe the boundary, not a second database model. Bind their
24643
+ fields to the existing database-source contract checker before delivery.
24644
+
24645
+ The named RPC adapter must invoke the continuation in the same database transaction
24646
+ used by the request-scoped Writer, or own the complete command instead; never execute
24647
+ both paths. Lock/check the version and state, reauthorize replays, and commit the
24648
+ business write, receipt, audit and outbox together. Do not put a network side effect
24649
+ inside this transaction. The refresh Job is an explicit unimplemented integration
24650
+ point, not an automatically scheduled workflow.
24651
+
24652
+ Compile the routes with the existing client generator and use @supacloud/query for
24653
+ identity-scoped caching/invalidation. There is deliberately no second hand-maintained
24654
+ HTTP client. A committed approval followed by a failed refresh is still committed;
24655
+ recover its receipt instead of resending. Use the host's standard error translation
24656
+ and requestId propagation; the scaffold does not claim to implement those adapters.
24657
+
24658
+ ## Required acceptance (not executed by generation)
24659
+
24660
+ \`\`\`gherkin
24661
+ Scenario: Authorization and tenant isolation
24662
+ Given two tenants and a revoked reviewer
24663
+ When list, detail, approval or receipt replay is requested
24664
+ Then unauthorized records and prior success receipts are not disclosed
24665
+
24666
+ Scenario: Atomic approval
24667
+ Given a draft and its authoritative version
24668
+ When an authorized reviewer approves and repeats the same request
24669
+ Then one transition, audit event and asynchronous intent commit
24670
+ And changed input or a stale version is rejected
24671
+
24672
+ Scenario: Unknown outcome
24673
+ Given a committed approval whose response was lost
24674
+ When the client recovers the original receipt
24675
+ Then it does not create a new approval or automatic compensation
24676
+
24677
+ Scenario: Delivery
24678
+ Given verified migrations, RLS and generated client contracts
24679
+ When the selected environment is deployed
24680
+ Then its receipt and authenticated list/detail/approval flow are read back
24681
+ \`\`\`
24682
+ `
24683
+ };
24684
+ }
24685
+
24245
24686
  // src/shared/tools/app-local-dev.ts
24246
24687
  import { spawn } from "node:child_process";
24247
24688
  import { readFile as readFile2 } from "node:fs/promises";
@@ -24733,6 +25174,12 @@ function requireIdentifier(value, flag) {
24733
25174
  }
24734
25175
  return trimmed;
24735
25176
  }
25177
+ function requireFeatureCapability(value, flag) {
25178
+ if (typeof value !== "string" || !/^[A-Za-z][A-Za-z0-9_.:-]{0,127}$/.test(value)) {
25179
+ throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", `feature requires an explicit valid --${flag}; capabilities are not inferred`);
25180
+ }
25181
+ return value;
25182
+ }
24736
25183
  function moduleScaffold(name) {
24737
25184
  return `import { Module } from "@supacloud/app";
24738
25185
 
@@ -24751,6 +25198,9 @@ function commandScaffold(moduleName, name) {
24751
25198
  @Command({
24752
25199
  name: ${JSON.stringify(`${moduleName}.${camelName(name)}`)},
24753
25200
  permission: ${JSON.stringify(`${moduleName}.${camelName(name)}`)},
25201
+ transaction: "required",
25202
+ audit: ${JSON.stringify(`${moduleName}.${camelName(name)}.completed`)},
25203
+ idempotency: "required",
24754
25204
  })
24755
25205
  export class ${pascalName(name)}Command {
24756
25206
  execute(): void {
@@ -24805,8 +25255,8 @@ export const ${prefix}Response = t.Object({});
24805
25255
  }
24806
25256
  async function generateScaffold(args) {
24807
25257
  const kind = args.kind;
24808
- if (!kind || !["module", "command", "query", "controller", "job", "contract", "resource"].includes(kind)) {
24809
- throw new ScaffoldError("SCAFFOLD_KIND_INVALID", "app generate requires --kind (module|command|query|controller|job|contract|resource)");
25258
+ if (!kind || !["module", "command", "query", "controller", "job", "contract", "resource", "feature"].includes(kind)) {
25259
+ throw new ScaffoldError("SCAFFOLD_KIND_INVALID", "app generate requires --kind (module|command|query|controller|job|contract|resource|feature)");
24810
25260
  }
24811
25261
  for (const flag of ["dry_run", "force"]) {
24812
25262
  if (args[flag] !== undefined && typeof args[flag] !== "boolean") {
@@ -24817,24 +25267,31 @@ async function generateScaffold(args) {
24817
25267
  const dir = args.dir ?? "src/features";
24818
25268
  if (dir !== ".")
24819
25269
  scaffoldPath(root, dir);
24820
- if (args.register_in !== undefined && kind !== "resource" && kind !== "module") {
24821
- throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "--register-in is only supported for a new module or resource");
25270
+ if (args.register_in !== undefined && kind !== "resource" && kind !== "module" && kind !== "feature") {
25271
+ throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "--register-in is only supported for a new module, resource or feature");
25272
+ }
25273
+ if ((kind === "resource" || kind === "feature") && (args.force || args.module !== undefined)) {
25274
+ throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", `${kind} creates its own module; use --name and optionally --register-in, never --force or --module`);
24822
25275
  }
24823
- if (kind === "resource" && (args.force || args.module !== undefined)) {
24824
- throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "resource creates its own module; use --name and optionally --register-in, never --force or --module");
25276
+ if (kind !== "feature" && (args.permission !== undefined || args.rpc !== undefined)) {
25277
+ throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "--permission and --rpc are only supported for feature generation");
24825
25278
  }
25279
+ const featureGovernance = kind === "feature" ? {
25280
+ permission: requireFeatureCapability(args.permission, "permission"),
25281
+ rpc: requireFeatureCapability(args.rpc, "rpc")
25282
+ } : undefined;
24826
25283
  const writes = [];
24827
25284
  let modulePath;
24828
25285
  let moduleSymbol;
24829
25286
  let contractName;
24830
- if (kind === "module" || kind === "resource") {
25287
+ if (kind === "module" || kind === "resource" || kind === "feature") {
24831
25288
  const name = requireIdentifier(args.name, "name");
24832
- modulePath = join13(dir, name, `${name}.module.ts`).replaceAll("\\", "/");
24833
- moduleSymbol = `${pascalName(name)}Module`;
25289
+ modulePath = join13(dir, name, kind === "feature" ? `${name}.feature.ts` : `${name}.module.ts`).replaceAll("\\", "/");
25290
+ moduleSymbol = kind === "feature" ? `${pascalName(name)}Feature` : `${pascalName(name)}Module`;
24834
25291
  if (kind === "module")
24835
25292
  writes.push({ path: modulePath, content: moduleScaffold(name), overwrite: args.force === true });
24836
25293
  else
24837
- for (const [file, content] of Object.entries(resourceScaffold(name, pascalName(name)))) {
25294
+ for (const [file, content] of Object.entries(kind === "feature" ? featureScaffold(name, pascalName(name), featureGovernance) : resourceScaffold(name, pascalName(name)))) {
24838
25295
  writes.push({ path: join13(dir, name, file).replaceAll("\\", "/"), content });
24839
25296
  }
24840
25297
  } else {
@@ -24896,6 +25353,8 @@ async function generateScaffold(args) {
24896
25353
  lines.push("No files written. Omit --dry-run to apply this generation.");
24897
25354
  if (kind === "resource")
24898
25355
  lines.push("Service and controller are registered in the generated module. No root module is guessed.", "Use --register-in <module.ts> to add an explicit parent import. Implement the read port before exposing the resource.", "Run app compile, app check --format json and your tests. No writes, permissions or persistence are inferred.");
25356
+ if (kind === "feature")
25357
+ lines.push(`Approval feature scaffold ${args.dry_run ? "planned" : "created"}; no database, authorization or job adapter is fabricated.`, "Bind the explicit RPC in compiler capabilities and runtime governance. Review the generated delivery checklist.", "Generate the client through app compile; no handwritten transport or database schema is created.");
24899
25358
  if (contractName) {
24900
25359
  const prefix = pascalName(contractName);
24901
25360
  lines.push("", "Next, bind the contract to a route so the compiler can check route/schema drift:", ` import { ${prefix}Body, ${prefix}Response } from "./contracts/${contractName}.contract";`, ` @Post("/", { body: ${prefix}Body, responses: { 200: ${prefix}Response } })`, "Fill in the schema fields and reuse them in command decoders; never widen them to unknown.");
@@ -26185,15 +26644,17 @@ function registerAppTools(server, options = {}) {
26185
26644
  "promote-status",
26186
26645
  "promote-reconcile"
26187
26646
  ]), "App action; promote obtains and verifies its plan before one mutation request"),
26188
- kind: optional(stringEnum(["module", "command", "query", "controller", "job", "contract", "resource"]), "[generate] Scaffold kind"),
26647
+ kind: optional(stringEnum(["module", "command", "query", "controller", "job", "contract", "resource", "feature"]), "[generate] Scaffold kind"),
26189
26648
  template: optional(stringEnum(["minimal", "http", "command", "edge"]), "[init] Minimal application by default; explicit http/command/edge recipes"),
26190
26649
  name: optional(String2(), "[init/generate] Project or object name"),
26191
26650
  module: optional(String2(), "[generate] Target feature module (required for command/query/controller)"),
26651
+ permission: optional(String2(), "[generate feature] Explicit approval permission; not inferred from name"),
26652
+ rpc: optional(String2(), "[generate feature] Explicit durable RPC capability; must be registered by the host"),
26192
26653
  dir: optional(String2(), "[generate] Feature root directory (default: src/features)"),
26193
- force: optional(Boolean2(), "[generate] Overwrite a single regular file; resource/controller never overwrite"),
26654
+ force: optional(Boolean2(), "[generate] Overwrite a single regular file; resource/feature/controller never overwrite"),
26194
26655
  dry_run: optional(Boolean2(), "[generate] Validate and preview the complete change set without writing"),
26195
26656
  "dry-run": optional(Boolean2(), "[generate] CLI alias of dry_run"),
26196
- register_in: optional(String2(), "[generate] Existing parent module file for module/resource; uses the compiler AST editor"),
26657
+ register_in: optional(String2(), "[generate] Existing parent module file for module/resource/feature; uses the compiler AST editor"),
26197
26658
  "register-in": optional(String2(), "[generate] CLI alias of register_in"),
26198
26659
  root: optional(String2(), "[dev/plan/build/release/compile/check] Project directory containing supacloud.config.ts (default: current directory)"),
26199
26660
  include: optional(String2(), "[dev/compile/check] Comma-separated glob patterns for source files"),
@@ -26215,14 +26676,16 @@ function registerAppTools(server, options = {}) {
26215
26676
  var APP_ALIAS_ACTIONS = ["generate", "compile", "check", "graph", "explain", "context", "doctor", "fix"];
26216
26677
  function registerAppAliases(server) {
26217
26678
  const schema = {
26218
- kind: optional(stringEnum(["module", "command", "query", "controller", "job", "contract", "resource"]), "[generate] Scaffold kind"),
26679
+ kind: optional(stringEnum(["module", "command", "query", "controller", "job", "contract", "resource", "feature"]), "[generate] Scaffold kind"),
26219
26680
  name: optional(String2(), "[generate] Object name"),
26220
26681
  module: optional(String2(), "[generate] Target feature module"),
26682
+ permission: optional(String2(), "[generate feature] Explicit approval permission; not inferred from name"),
26683
+ rpc: optional(String2(), "[generate feature] Explicit durable RPC capability; must be registered by the host"),
26221
26684
  dir: optional(String2(), "[generate] Feature root directory (default: src/features)"),
26222
- force: optional(Boolean2(), "[generate] Overwrite a single regular file; resource/controller never overwrite"),
26685
+ force: optional(Boolean2(), "[generate] Overwrite a single regular file; resource/feature/controller never overwrite"),
26223
26686
  dry_run: optional(Boolean2(), "[generate] Validate and preview the complete change set without writing"),
26224
26687
  "dry-run": optional(Boolean2(), "[generate] CLI alias of dry_run"),
26225
- register_in: optional(String2(), "[generate] Existing parent module file for module/resource; uses the compiler AST editor"),
26688
+ register_in: optional(String2(), "[generate] Existing parent module file for module/resource/feature; uses the compiler AST editor"),
26226
26689
  "register-in": optional(String2(), "[generate] CLI alias of register_in"),
26227
26690
  root: optional(String2(), "Project directory containing supacloud.config.ts (default: current directory)"),
26228
26691
  include: optional(String2(), "[compile/check] Comma-separated glob patterns for source files"),
@@ -26262,7 +26725,7 @@ import {
26262
26725
  reverseDatabase
26263
26726
  } from "@supacloud/db";
26264
26727
  import { parseDatabaseSourcesConfig } from "@supacloud/db/source-contracts";
26265
- var hash3 = (content) => createHash7("sha256").update(content).digest("hex");
26728
+ var hash4 = (content) => createHash7("sha256").update(content).digest("hex");
26266
26729
  var result = (value, isError = false) => ({
26267
26730
  isError,
26268
26731
  content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
@@ -26393,7 +26856,7 @@ async function initializeCandidate(stage, majorVersion = 17) {
26393
26856
  await mkdir6(join14(stage, "supabase/migrations"), { recursive: true });
26394
26857
  await mkdir6(join14(stage, "supabase/schemas"), { recursive: true });
26395
26858
  await writeFile5(join14(stage, "supabase/config.toml"), [
26396
- `project_id = "supacloud-candidate-${hash3(stage).slice(0, 12)}"`,
26859
+ `project_id = "supacloud-candidate-${hash4(stage).slice(0, 12)}"`,
26397
26860
  "[db]",
26398
26861
  `major_version = ${majorVersion}`,
26399
26862
  "[db.migrations]",
@@ -26521,7 +26984,7 @@ async function migrations(root, path) {
26521
26984
  const files = [];
26522
26985
  for (const file of names) {
26523
26986
  const text = await readFile5(await projectPath2(root, `${path}/${file}`), "utf8");
26524
- files.push({ file, text, sha256: hash3(text) });
26987
+ files.push({ file, text, sha256: hash4(text) });
26525
26988
  }
26526
26989
  return { dir, files };
26527
26990
  }
@@ -26551,7 +27014,7 @@ async function migrationAction(args, root, options) {
26551
27014
  files: input.files.map(({ file, sha256 }) => ({ file, sha256 })),
26552
27015
  dryRun
26553
27016
  };
26554
- const digest = hash3(JSON.stringify(report));
27017
+ const digest = hash4(JSON.stringify(report));
26555
27018
  if (dryRun.isError)
26556
27019
  return result({ ...report, ok: false, digest, executionPerformed: false }, true);
26557
27020
  if (args.action === "plan")
@@ -28168,7 +28631,7 @@ async function requireProjectDeploymentProtocol(http, projectRef, environmentId)
28168
28631
  throw new Error("DEPLOYMENT_PROTOCOL_MISMATCH: Upgrade the SupaCloud server and CLI together (`npm install -g @supacloud/cli@latest`), then retry `supacloud deploy`. No upload or deployment was attempted; legacy endpoints will not be used.");
28169
28632
  }
28170
28633
  }
28171
- var operation = "applications.project_deploy";
28634
+ var operation2 = "applications.project_deploy";
28172
28635
  var scopeSchema = _Object_({
28173
28636
  project_ref: String2({ pattern: "^[a-z0-9-]{1,20}$" }),
28174
28637
  application_id: ApplicationIdSchema,
@@ -28217,7 +28680,7 @@ async function runProjectDeployment(http, args) {
28217
28680
  ...args.web_release === undefined ? {} : { web_release: args.web_release }
28218
28681
  };
28219
28682
  if (!Check2(scopeSchema, scope) || !Check2(ApplicationDeployRequestSchema, request) || request.configuration_id !== undefined && request.configuration_write !== undefined && request.configuration_id !== request.configuration_write.configuration_id) {
28220
- return releaseControlFailure(operation, "INVALID_RESPONSE", null);
28683
+ return releaseControlFailure(operation2, "INVALID_RESPONSE", null);
28221
28684
  }
28222
28685
  const expected = { ...scope, request };
28223
28686
  const identity = {
@@ -28235,13 +28698,13 @@ async function runProjectDeployment(http, args) {
28235
28698
  }, { timeoutMs: 120000, maxJsonBytes: 131072, responseTimeoutMs: 30000 });
28236
28699
  if (result.ok && result.status === 200) {
28237
28700
  try {
28238
- return releaseControlSuccess(operation, {
28701
+ return releaseControlSuccess(operation2, {
28239
28702
  ...responseReceipt(result.data, expected),
28240
28703
  deployment_id: mutation_id
28241
28704
  });
28242
28705
  } catch {}
28243
28706
  } else if (!result.ok && !result.transportError && !result.responseReadError && result.status < 500 && result.status !== 408) {
28244
- return releaseControlMutationFailure(operation, result, identity);
28707
+ return releaseControlMutationFailure(operation2, result, identity);
28245
28708
  }
28246
28709
  const observed = await http.get(`${endpoint}/${encodeURIComponent(mutation_id)}`, {
28247
28710
  retry: false,
@@ -28255,14 +28718,14 @@ async function runProjectDeployment(http, args) {
28255
28718
  if (state.deployment_id !== mutation_id || state.project_ref !== scope.project_ref || state.application_id !== scope.application_id || state.environment_id !== scope.environment_id || state.status !== "succeeded" || state.phase !== "verifying")
28256
28719
  throw new Error;
28257
28720
  const receipt = parseApplicationDeployResult(state.receipt, expected);
28258
- return releaseControlSuccess(operation, {
28721
+ return releaseControlSuccess(operation2, {
28259
28722
  ...receipt,
28260
28723
  deployment_id: mutation_id,
28261
28724
  replayed: true,
28262
28725
  execution_performed: false
28263
28726
  });
28264
28727
  } catch {
28265
- return releaseControlFailure(operation, "OUTCOME_UNKNOWN", result.transportError ? null : result.status, identity);
28728
+ return releaseControlFailure(operation2, "OUTCOME_UNKNOWN", result.transportError ? null : result.status, identity);
28266
28729
  }
28267
28730
  }
28268
28731
 
@@ -29392,7 +29855,7 @@ function registerDeveloperExperienceTools(server, options = {}) {
29392
29855
  // package.json
29393
29856
  var package_default = {
29394
29857
  name: "@supacloud/cli",
29395
- version: "0.96.1",
29858
+ version: "0.97.0",
29396
29859
  description: "Project-scoped CLI for SupaCloud users",
29397
29860
  type: "module",
29398
29861
  main: "./dist/index.js",
@@ -29444,14 +29907,14 @@ var package_default = {
29444
29907
  directory: "packages/cli"
29445
29908
  },
29446
29909
  dependencies: {
29447
- "@supacloud/compiler": "^0.46.0",
29448
- "@supacloud/delivery": "0.17.0",
29910
+ "@supacloud/compiler": "^0.46.1",
29911
+ "@supacloud/delivery": "0.18.0",
29449
29912
  "@supacloud/db": "0.17.0",
29450
29913
  typebox: "1.3.36"
29451
29914
  },
29452
29915
  overrides: {
29453
- "@supacloud/contracts": "0.8.1",
29454
- "@supacloud/delivery": "0.17.0",
29916
+ "@supacloud/contracts": "0.9.0",
29917
+ "@supacloud/delivery": "0.18.0",
29455
29918
  "@graphql-tools/utils": "^12.0.3"
29456
29919
  },
29457
29920
  devDependencies: {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ /**
2
+ * An editable vertical feature slice. The generated ports are deliberately
3
+ * fail-closed: database access, authorization and durable command adapters
4
+ * remain application-owned and cannot be fabricated by the scaffold.
5
+ */
6
+ export declare function featureScaffold(name: string, className: string, governance: {
7
+ permission: string;
8
+ rpc: string;
9
+ }): Record<string, string>;
@@ -65,10 +65,12 @@ export interface AppToolArguments {
65
65
  ttl_seconds?: number;
66
66
  wait?: boolean;
67
67
  timeout_seconds?: number;
68
- kind?: "module" | "command" | "query" | "controller" | "job" | "contract" | "resource";
68
+ kind?: "module" | "command" | "query" | "controller" | "job" | "contract" | "resource" | "feature";
69
69
  template?: "minimal" | "http" | "command" | "edge";
70
70
  name?: string;
71
71
  module?: string;
72
+ permission?: string;
73
+ rpc?: string;
72
74
  dir?: string;
73
75
  force?: boolean;
74
76
  dry_run?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.96.1",
3
+ "version": "0.97.0",
4
4
  "description": "Project-scoped CLI for SupaCloud users",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -52,14 +52,14 @@
52
52
  "directory": "packages/cli"
53
53
  },
54
54
  "dependencies": {
55
- "@supacloud/compiler": "^0.46.0",
56
- "@supacloud/delivery": "0.17.0",
55
+ "@supacloud/compiler": "^0.46.1",
56
+ "@supacloud/delivery": "0.18.0",
57
57
  "@supacloud/db": "0.17.0",
58
58
  "typebox": "1.3.36"
59
59
  },
60
60
  "overrides": {
61
- "@supacloud/contracts": "0.8.1",
62
- "@supacloud/delivery": "0.17.0",
61
+ "@supacloud/contracts": "0.9.0",
62
+ "@supacloud/delivery": "0.18.0",
63
63
  "@graphql-tools/utils": "^12.0.3"
64
64
  },
65
65
  "devDependencies": {