@varde-flyt/vfac 0.15.2 → 0.16.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.
Files changed (3) hide show
  1. package/README.md +4 -2
  2. package/dist/vfac.mjs +924 -75
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -91,8 +91,10 @@ operations of a resource are listed newest first, one page at a time; the
91
91
  vfac get operations --resource <productInstanceId>
92
92
  ```
93
93
 
94
- Secrets are supplied per apply and never belong in the manifest — a manifest is a
95
- file you commit. A value for a setting the Product declares secret is refused
94
+ Secrets are passed as `--secret` inputs, beside the manifest and never in it — a manifest is a
95
+ file you commit. Pass one when a resource is created, and afterwards only on a run that sets or
96
+ changes one: a secret sent is a secret written, so a run that repeats one plans `UPDATE` rather
97
+ than `NO_CHANGE`. A value for a setting the Product declares secret is refused
96
98
  at every door by the platform, and by `vfac` before the file is sent when the
97
99
  file pins the release the catalog currently offers. **Plan the run that sets one with the same `--secret` inputs the
98
100
  apply will carry**: `plan` takes the flag exactly as `apply` does, and without it
package/dist/vfac.mjs CHANGED
@@ -12337,12 +12337,12 @@ var CustomerConfigurationSchema = CustomerConfigurationObjectSchema.superRefine(
12337
12337
  });
12338
12338
  }
12339
12339
  }
12340
- const duplicates = (config.required ?? []).filter((name, index, all) => all.indexOf(name) !== index);
12341
- if (duplicates.length > 0) {
12340
+ const duplicates2 = (config.required ?? []).filter((name, index, all) => all.indexOf(name) !== index);
12341
+ if (duplicates2.length > 0) {
12342
12342
  ctx.addIssue({
12343
12343
  code: external_exports.ZodIssueCode.custom,
12344
12344
  path: ["required"],
12345
- message: `Duplicate required entries: ${[...new Set(duplicates)].join(", ")}.`
12345
+ message: `Duplicate required entries: ${[...new Set(duplicates2)].join(", ")}.`
12346
12346
  });
12347
12347
  }
12348
12348
  checkDependentRequired(config, ctx);
@@ -14322,12 +14322,619 @@ function settingContainer(container, requiredByParent) {
14322
14322
  };
14323
14323
  }
14324
14324
 
14325
+ // ../log-access-contract/dist/operations.js
14326
+ var ALL_LOG_ACCESS_OPS = Object.freeze([
14327
+ "append",
14328
+ "query",
14329
+ "aggregate",
14330
+ "analyze"
14331
+ ]);
14332
+
14333
+ // ../log-access-contract/dist/limits.js
14334
+ var MAX_RECORDS_PER_APPEND = 500;
14335
+ var MAX_RECORD_BYTES = 16384;
14336
+ var MAX_APPEND_BYTES = 1048576;
14337
+ var MAX_APPEND_BODY_BYTES = 2 * MAX_APPEND_BYTES;
14338
+ var MAX_ATTRIBUTION_LENGTH = 128;
14339
+ var MAX_RECORD_FIELDS = 32;
14340
+ var MAX_FIELD_NAME_LENGTH = 64;
14341
+ var MAX_FIELD_VALUE_LENGTH = 2048;
14342
+ var MAX_QUERY_LIMIT = 1e3;
14343
+ var MAX_QUERY_WINDOW_SECONDS = 86400;
14344
+ var MAX_QUERY_FILTERS = 8;
14345
+ var MAX_AGGREGATE_WINDOW_SECONDS = 604800;
14346
+ var MIN_AGGREGATE_BUCKET_SECONDS = 60;
14347
+ var MAX_AGGREGATE_BUCKETS = 1440;
14348
+ var MAX_AGGREGATE_GROUPS = 100;
14349
+ var MAX_RECORDS_PER_TIMESTAMP = 5e3;
14350
+ var MAX_ANALYSIS_WINDOW_SECONDS = 604800;
14351
+ var MAX_ANALYSIS_PREDICATES = 8;
14352
+ var MAX_ANALYSIS_PREDICATE_VALUES = 16;
14353
+ var MAX_ANALYSIS_VALUE_BYTES = 16384;
14354
+ var MAX_ANALYSIS_GROUP_BY_FIELDS = 2;
14355
+ var MAX_ANALYSIS_GROUPS = 100;
14356
+ var MIN_ANALYSIS_BUCKET_SECONDS = MIN_AGGREGATE_BUCKET_SECONDS;
14357
+ var MAX_ANALYSIS_BUCKETS = MAX_AGGREGATE_BUCKETS;
14358
+ var MAX_ANALYSIS_CELLS = 1e4;
14359
+
14360
+ // ../log-access-contract/dist/wire.js
14361
+ var LOG_ACCESS_REQUEST_HEADERS = Object.freeze({
14362
+ signature: "x-vfac-signature",
14363
+ operationId: "x-vfac-operation-id",
14364
+ requestedAt: "x-vfac-requested-at",
14365
+ productInstanceId: "x-vfac-product-instance-id"
14366
+ });
14367
+ var SIGNING_CANONICAL_FIELDS = Object.freeze([
14368
+ "method",
14369
+ "path",
14370
+ "operationId",
14371
+ "requestedAt",
14372
+ "sha256(body)"
14373
+ ]);
14374
+ var LOG_SEVERITIES = Object.freeze(["debug", "info", "warn", "error", "fatal"]);
14375
+ var RESERVED_RECORD_FIELDS = Object.freeze([
14376
+ "message",
14377
+ "severity",
14378
+ "observedAt",
14379
+ "receivedAt",
14380
+ "attributedTo"
14381
+ ]);
14382
+ var LOG_FIELD_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_.-]*$/;
14383
+ var UNGROUPABLE_RECORD_FIELDS = Object.freeze([
14384
+ "message",
14385
+ "observedAt",
14386
+ "receivedAt"
14387
+ ]);
14388
+ var ANALYSIS_TEXT_OPERATORS = Object.freeze([
14389
+ "eq",
14390
+ "neq",
14391
+ "contains",
14392
+ "notContains"
14393
+ ]);
14394
+ var ANALYSIS_LIST_OPERATORS = Object.freeze(["in", "notIn"]);
14395
+ var ANALYSIS_PRESENCE_OPERATORS = Object.freeze(["exists", "notExists"]);
14396
+ var ANALYSIS_NUMERIC_OPERATORS = Object.freeze(["gt", "gte", "lt", "lte"]);
14397
+ var ANALYSIS_PREDICATE_OPERATORS = Object.freeze([
14398
+ ...ANALYSIS_TEXT_OPERATORS,
14399
+ ...ANALYSIS_LIST_OPERATORS,
14400
+ ...ANALYSIS_PRESENCE_OPERATORS,
14401
+ ...ANALYSIS_NUMERIC_OPERATORS
14402
+ ]);
14403
+ var ANALYSIS_COUNTING_MEASURES = Object.freeze(["count", "rate"]);
14404
+ var ANALYSIS_FIELD_MEASURES = Object.freeze([
14405
+ "sum",
14406
+ "avg",
14407
+ "min",
14408
+ "max",
14409
+ "percentile"
14410
+ ]);
14411
+ var ANALYSIS_MEASURES = Object.freeze([
14412
+ ...ANALYSIS_COUNTING_MEASURES,
14413
+ ...ANALYSIS_FIELD_MEASURES
14414
+ ]);
14415
+ var ANALYSIS_PERCENTILES = Object.freeze(["p50", "p90", "p95", "p99"]);
14416
+ var ANALYSIS_ORDERS = Object.freeze(["desc", "asc"]);
14417
+ var ANALYSIS_RESULT_KINDS = Object.freeze(["scalar", "groups", "timeseries"]);
14418
+
14419
+ // ../log-access-contract/dist/refusals.js
14420
+ function listed(names, conjunction = "or") {
14421
+ const quoted = names.map((name) => `\`${name}\``);
14422
+ return quoted.length < 2 ? quoted.join("") : `${quoted.slice(0, -1).join(", ")} ${conjunction} ${quoted[quoted.length - 1]}`;
14423
+ }
14424
+ var ROUTES = ALL_LOG_ACCESS_OPS.map((op) => `/${op}`);
14425
+ var LOG_ACCESS_REFUSALS = Object.freeze({
14426
+ missing_records: {
14427
+ status: 400,
14428
+ meaning: "no `records`, or one that is not a non-empty list"
14429
+ },
14430
+ too_many_records: {
14431
+ status: 400,
14432
+ meaning: `more than ${MAX_RECORDS_PER_APPEND} records in one append`
14433
+ },
14434
+ append_too_large: {
14435
+ status: 400,
14436
+ meaning: `the records exceed ${MAX_APPEND_BYTES} bytes as compact JSON, or the request body exceeds ${MAX_APPEND_BODY_BYTES} bytes`
14437
+ },
14438
+ record_too_large: {
14439
+ status: 400,
14440
+ meaning: `one record exceeds ${MAX_RECORD_BYTES} bytes. A log line, not a document`
14441
+ },
14442
+ too_many_fields: {
14443
+ status: 400,
14444
+ meaning: `one record carries more than ${MAX_RECORD_FIELDS} fields`
14445
+ },
14446
+ invalid_field: {
14447
+ status: 400,
14448
+ meaning: "a field name or value is empty, over-long, or carries a control character. Field names are structured keys and never stream labels, so none of them changes what your records are filed under"
14449
+ },
14450
+ reserved_field: {
14451
+ status: 400,
14452
+ meaning: `a field is named ${listed(RESERVED_RECORD_FIELDS)}. Those are the record\u2019s own keys and are stored beside your fields, so a field of the same name would overwrite one or be overwritten by it \u2014 rename the field`
14453
+ },
14454
+ invalid_attribution: {
14455
+ status: 400,
14456
+ meaning: `\`attributedTo\` is not one identifier of at most ${MAX_ATTRIBUTION_LENGTH} letters, digits, \`.\`, \`_\`, \`:\` or \`-\` starting with a letter or digit`
14457
+ },
14458
+ invalid_timestamp: {
14459
+ status: 400,
14460
+ meaning: "`observedAt` is not an RFC 3339 instant. The service stamps its own receipt time as well, and that one is what ordering uses"
14461
+ },
14462
+ invalid_severity: {
14463
+ status: 400,
14464
+ meaning: "`severity` is not one of the levels this contract defines"
14465
+ },
14466
+ invalid_window: {
14467
+ status: 400,
14468
+ meaning: `\`from\` and \`to\` are not an RFC 3339 range, are inverted, or span more than ${MAX_QUERY_WINDOW_SECONDS} seconds. Not the retention window \u2014 how much ONE query scans`
14469
+ },
14470
+ invalid_limit: {
14471
+ status: 400,
14472
+ meaning: `\`limit\` is not a whole number in 1\u2026${MAX_QUERY_LIMIT}`
14473
+ },
14474
+ invalid_filters: {
14475
+ status: 400,
14476
+ meaning: `\`filters\` is not a list of \`{field, equals}\`, or carries more than ${MAX_QUERY_FILTERS} entries`
14477
+ },
14478
+ invalid_cursor: {
14479
+ status: 400,
14480
+ meaning: "the `cursor` did not come from this service, or is not for this query. A page is resumed with the cursor the previous page returned, verbatim, and never composed"
14481
+ },
14482
+ invalid_aggregate: {
14483
+ status: 400,
14484
+ meaning: `the aggregate is not one this service answers: a window over ${MAX_AGGREGATE_WINDOW_SECONDS} seconds, a \`bucketSeconds\` under ${MIN_AGGREGATE_BUCKET_SECONDS} or not dividing the window into at most ${MAX_AGGREGATE_BUCKETS} whole buckets, a \`groupBy\` that is not a field identifier or names \`message\`, \`observedAt\` or \`receivedAt\`, a \`limit\` outside 1\u2026${MAX_AGGREGATE_GROUPS} or without \`groupBy\`, or both \`bucketSeconds\` and \`groupBy\``
14485
+ },
14486
+ invalid_analysis: {
14487
+ status: 400,
14488
+ meaning: `the analysis is not one this service answers: a member it does not define, at any level, or one missing that it needs; a window over ${MAX_ANALYSIS_WINDOW_SECONDS} seconds; an empty \`where\` or an empty list in it; more than ${MAX_ANALYSIS_PREDICATES} predicates counted everywhere, a group inside a group, an empty group, or an \`in\`/\`notIn\` list that is not 1\u2026${MAX_ANALYSIS_PREDICATE_VALUES} distinct values; a field that is not an identifier; a value that is empty, longer than ${MAX_FIELD_VALUE_LENGTH} characters, not well-formed Unicode or carrying a control character, or values together over ${MAX_ANALYSIS_VALUE_BYTES} bytes; a number that is not finite; a measure or a numeric predicate on ${listed(RESERVED_RECORD_FIELDS)}; more than ${MAX_ANALYSIS_GROUP_BY_FIELDS} or repeated \`groupBy\` fields, or one naming ${listed(UNGROUPABLE_RECORD_FIELDS)}; a \`limit\` missing with \`groupBy\`, present without it or outside 1\u2026${MAX_ANALYSIS_GROUPS}; an \`order\` without \`groupBy\`; a \`bucketSeconds\` under ${MIN_ANALYSIS_BUCKET_SECONDS}, a \`from\` or \`to\` that is not a whole multiple of it since 1970-01-01T00:00:00Z, or more than ${MAX_ANALYSIS_BUCKETS} buckets; or buckets times \`limit\` over ${MAX_ANALYSIS_CELLS}`
14489
+ },
14490
+ analysis_timed_out: {
14491
+ status: 504,
14492
+ meaning: "the provider did not answer within the time one analysis gets. The same analysis is likely to time out again, so narrow the window, the selection or the grouping rather than repeating it \u2014 unlike `log_store_error`, this says the question was too expensive rather than that the provider faltered"
14493
+ },
14494
+ analysis_too_broad: {
14495
+ status: 400,
14496
+ meaning: "ranking the groups met more distinct values than can be ranked in one answer. Not transient \u2014 narrow the window or the selection, or group by a field with fewer values"
14497
+ },
14498
+ ingest_suspended: {
14499
+ status: 403,
14500
+ meaning: "this Product Instance may read its records and may not add one. Its Tenant is being drained or is not active \u2014 see `writable` on the authorization record"
14501
+ },
14502
+ query_not_granted: {
14503
+ status: 403,
14504
+ meaning: "this Product Instance may add records and may not read them back"
14505
+ },
14506
+ log_store_error: {
14507
+ status: 502,
14508
+ meaning: "the provider refused or did not answer. Not yours to fix, and the one refusal here worth retrying"
14509
+ },
14510
+ timestamp_group_too_large: {
14511
+ status: 502,
14512
+ meaning: `more than ${MAX_RECORDS_PER_TIMESTAMP} records share one timestamp, so this page cannot be answered without skipping some. Refused rather than answered incompletely; not yours to fix`
14513
+ },
14514
+ log_store_capacity: {
14515
+ status: 503,
14516
+ meaning: "the provider is rate-limiting or over quota. Retry with backoff; records already accepted are kept"
14517
+ }
14518
+ });
14519
+ var HANDLER_REFUSALS = Object.freeze({
14520
+ invalid_body: { status: 400, meaning: "the request body is not JSON" },
14521
+ invalid_op: {
14522
+ status: 400,
14523
+ meaning: `no \`op\`, or one this route does not serve \u2014 ${listed(ROUTES, "and")} each serve the operation they are named after`
14524
+ },
14525
+ forbidden: {
14526
+ status: 403,
14527
+ meaning: "the credential verified and does not hold this operation\u2019s permission. Not an authentication failure \u2014 see 401 for those"
14528
+ },
14529
+ log_access_unconfigured: {
14530
+ status: 503,
14531
+ meaning: "this deployment cannot present a working provider credential \u2014 it has none, or the provider refused the one it has \u2014 so it can broker nothing. An operator problem, not a caller one, and not worth retrying at a caller\u2019s pace: it clears when an operator or the platform replaces the credential"
14532
+ }
14533
+ });
14534
+ var AUTHENTICATION_REFUSALS = Object.freeze({
14535
+ unauthorized: {
14536
+ status: 401,
14537
+ meaning: "the signature, the headers, the clock, or an instance that does not exist. Which one is never said"
14538
+ },
14539
+ operation_replay: {
14540
+ status: 409,
14541
+ meaning: "that operation id has been used inside the replay window. Sign a retry with a NEW one"
14542
+ },
14543
+ unavailable: {
14544
+ status: 503,
14545
+ meaning: "the platform could not read this instance\u2019s credential. Transient; retry"
14546
+ },
14547
+ replay_store_unavailable: {
14548
+ status: 503,
14549
+ meaning: "the replay store did not answer, so this request cannot be proved to be a first use. Refused rather than accepted; retry with a fresh operation id"
14550
+ }
14551
+ });
14552
+ var ALL_REFUSALS = Object.freeze({
14553
+ ...LOG_ACCESS_REFUSALS,
14554
+ ...HANDLER_REFUSALS
14555
+ });
14556
+
14557
+ // ../log-access-contract/dist/log-semantics.js
14558
+ var LOG_EVENT_FIELD = "event";
14559
+ var LOG_SCHEMA_REF_FIELD = "vfac_log_schema";
14560
+ var LOG_SEMANTICS_FORMAT_V1 = "log-semantics.vardeflyt.no/v1";
14561
+
14562
+ // ../log-access-contract/dist/analysis-examples.js
14563
+ var ANALYZE_REQUEST_EXAMPLES = Object.freeze([
14564
+ {
14565
+ id: "p95-per-operation-over-time",
14566
+ asks: "The 95th percentile of `duration_ms` per operation, in five-minute buckets, over the production records that are errors or failed",
14567
+ request: {
14568
+ op: "analyze",
14569
+ from: "2026-09-21T00:00:00Z",
14570
+ to: "2026-09-22T00:00:00Z",
14571
+ where: {
14572
+ all: [{ field: "environment", op: "eq", value: "prod" }],
14573
+ any: [
14574
+ { field: "severity", op: "eq", value: "error" },
14575
+ { field: "outcome", op: "eq", value: "failed" }
14576
+ ]
14577
+ },
14578
+ measure: { op: "percentile", field: "duration_ms", percentile: "p95" },
14579
+ groupBy: ["operation"],
14580
+ bucketSeconds: 300,
14581
+ limit: 20
14582
+ }
14583
+ },
14584
+ {
14585
+ id: "error-count",
14586
+ asks: "How many records in a day were errors or worse",
14587
+ request: {
14588
+ op: "analyze",
14589
+ from: "2026-09-21T00:00:00Z",
14590
+ to: "2026-09-22T00:00:00Z",
14591
+ where: { all: [{ field: "severity", op: "in", values: ["error", "fatal"] }] },
14592
+ measure: { op: "count" }
14593
+ }
14594
+ },
14595
+ {
14596
+ id: "slowest-routes",
14597
+ asks: "The ten slowest checkout routes of the week per environment, counting only requests that took a second or more",
14598
+ request: {
14599
+ op: "analyze",
14600
+ from: "2026-09-15T00:00:00Z",
14601
+ to: "2026-09-22T00:00:00Z",
14602
+ where: {
14603
+ all: [
14604
+ { field: "duration_ms", op: "gte", value: 1e3 },
14605
+ { field: "route", op: "contains", value: "/checkout" }
14606
+ ]
14607
+ },
14608
+ measure: { op: "max", field: "duration_ms" },
14609
+ groupBy: ["route", "environment"],
14610
+ limit: 10,
14611
+ order: "desc"
14612
+ }
14613
+ },
14614
+ {
14615
+ id: "grouped-alternatives",
14616
+ asks: "How many records in each environment either failed or took a second or more, counting only the two checkout routes",
14617
+ request: {
14618
+ op: "analyze",
14619
+ from: "2026-09-21T00:00:00Z",
14620
+ to: "2026-09-22T00:00:00Z",
14621
+ where: {
14622
+ all: [
14623
+ {
14624
+ any: [
14625
+ { field: "route", op: "eq", value: "/checkout" },
14626
+ { field: "route", op: "eq", value: "/checkout/pay" }
14627
+ ]
14628
+ },
14629
+ {
14630
+ any: [
14631
+ { field: "outcome", op: "eq", value: "failed" },
14632
+ { field: "duration_ms", op: "gte", value: 1e3 }
14633
+ ]
14634
+ }
14635
+ ]
14636
+ },
14637
+ measure: { op: "count" },
14638
+ groupBy: ["environment"],
14639
+ limit: 5
14640
+ }
14641
+ },
14642
+ {
14643
+ id: "hourly-rate",
14644
+ asks: "Records per second, hour by hour, over a day",
14645
+ request: {
14646
+ op: "analyze",
14647
+ from: "2026-09-21T00:00:00Z",
14648
+ to: "2026-09-22T00:00:00Z",
14649
+ measure: { op: "rate" },
14650
+ bucketSeconds: 3600
14651
+ }
14652
+ }
14653
+ ]);
14654
+ var ANALYZE_RESPONSE_EXAMPLES = Object.freeze({
14655
+ scalar: { ok: true, kind: "scalar", value: 124 },
14656
+ groups: {
14657
+ ok: true,
14658
+ kind: "groups",
14659
+ groups: [
14660
+ { dimensions: { route: "/checkout/pay", environment: "prod" }, value: 4210 },
14661
+ { dimensions: { route: "/checkout", environment: null }, value: 1830 }
14662
+ ],
14663
+ truncated: true
14664
+ },
14665
+ timeseries: {
14666
+ ok: true,
14667
+ kind: "timeseries",
14668
+ bucketSeconds: 300,
14669
+ series: [
14670
+ {
14671
+ dimensions: { operation: "deploy" },
14672
+ points: [
14673
+ { start: "2026-09-21T00:00:00.000Z", value: 431 },
14674
+ { start: "2026-09-21T00:05:00.000Z", value: null }
14675
+ ]
14676
+ }
14677
+ ],
14678
+ truncated: false
14679
+ }
14680
+ });
14681
+
14682
+ // ../log-access-contract/dist/workspace-ingest.js
14683
+ var WORKSPACE_INGEST_STATUSES = Object.freeze({
14684
+ 202: "accepted in full \u2014 the body is `{accepted}` and it equals the number of records sent. Anything short of that is not an acknowledgement",
14685
+ 400: "the batch is malformed; `error` is a record refusal code. Do not retry it unchanged",
14686
+ 401: "the credential is not valid for this address. Stop sending until the binding changes",
14687
+ 403: "the credential does not hold `logs.write`. Stop sending until the binding changes",
14688
+ 429: "the workspace is over its rate. Back off, honouring `Retry-After` when present",
14689
+ 503: "the workspace cannot take records now. Retry with backoff"
14690
+ });
14691
+
14692
+ // ../product-configuration/dist/log-semantics.js
14693
+ var MAX_LOG_SEMANTICS_EVENTS = 128;
14694
+ var MAX_LOG_SEMANTICS_FIELDS = 64;
14695
+ var MAX_LOG_EVENT_FIELDS = MAX_RECORD_FIELDS - 2;
14696
+ var MAX_LOG_FIELD_VALUES = 32;
14697
+ var MAX_LOG_CORRELATION_FIELDS = 8;
14698
+ var MAX_LOG_INTERPRETATION_NOTES = 12;
14699
+ var MAX_LOG_EVENT_NAME_LENGTH = 128;
14700
+ var MAX_LOG_SEMANTICS_BYTES = 131072;
14701
+ var LOG_FIELD_TYPES = Object.freeze(["string", "number", "boolean"]);
14702
+ var LOG_EVENT_DELIVERY = Object.freeze(["at-least-once", "best-effort"]);
14703
+ var LOG_EVENT_NAME_PATTERN = /^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)*$/;
14704
+ function hasControlCharacter(value) {
14705
+ for (let i = 0; i < value.length; i += 1) {
14706
+ const code = value.charCodeAt(i);
14707
+ if (code < 32 || code === 127)
14708
+ return true;
14709
+ }
14710
+ return false;
14711
+ }
14712
+ var TextSchema = (min, max) => external_exports.string().min(min).max(max).refine((value) => !hasControlCharacter(value), {
14713
+ message: "Descriptor text is one line, with no control characters."
14714
+ });
14715
+ var FieldNameSchema = external_exports.string().min(1).max(MAX_FIELD_NAME_LENGTH).regex(LOG_FIELD_NAME_PATTERN, "A field name follows the ingest wire rule.");
14716
+ var LogFieldValueSchema = external_exports.object({
14717
+ /** One value the field can hold, exactly as a record carries it. */
14718
+ value: TextSchema(1, 128),
14719
+ /** What that value means. */
14720
+ meaning: TextSchema(3, 300)
14721
+ }).strict();
14722
+ var LogFieldSchema = external_exports.object({
14723
+ name: FieldNameSchema,
14724
+ type: external_exports.enum(LOG_FIELD_TYPES),
14725
+ meaning: TextSchema(10, 400),
14726
+ /** The unit a number is measured in: `ms`, `bytes`, `EUR`. Numbers only. */
14727
+ unit: TextSchema(1, 32).optional(),
14728
+ /** Values a string field holds, each explained. Strings only. */
14729
+ values: external_exports.array(LogFieldValueSchema).min(1).max(MAX_LOG_FIELD_VALUES).optional(),
14730
+ /**
14731
+ * `true`: `values` is every value the field can hold, so a record carrying
14732
+ * another is out of contract. Absent: `values` explains the known ones.
14733
+ */
14734
+ closed: external_exports.literal(true).optional()
14735
+ }).strict();
14736
+ var LogEventFieldSchema = external_exports.object({
14737
+ /** A declared field this event's records carry. */
14738
+ field: FieldNameSchema,
14739
+ /** Whether every record of this event carries it. */
14740
+ required: external_exports.boolean()
14741
+ }).strict();
14742
+ var LogEventSchema = external_exports.object({
14743
+ name: external_exports.string().min(1).max(MAX_LOG_EVENT_NAME_LENGTH).regex(LOG_EVENT_NAME_PATTERN, "An event name is dotted lowercase segments."),
14744
+ meaning: TextSchema(10, 400),
14745
+ /** When the producer actually emits it — the condition, not the intent. */
14746
+ whenProduced: TextSchema(10, 400),
14747
+ delivery: external_exports.enum(LOG_EVENT_DELIVERY).optional(),
14748
+ /** The declared fields its records carry, beyond `event` and `vfac_log_schema`. */
14749
+ fields: external_exports.array(LogEventFieldSchema).max(MAX_LOG_EVENT_FIELDS)
14750
+ }).strict();
14751
+ var LogCorrelationSchema = external_exports.object({
14752
+ /** A declared field records can be joined on. */
14753
+ field: FieldNameSchema,
14754
+ meaning: TextSchema(10, 300)
14755
+ }).strict();
14756
+ var LogInterpretationSchema = external_exports.object({
14757
+ /** What it means when a field an event may carry is absent. */
14758
+ absentField: TextSchema(10, 600),
14759
+ /** Whether a record can arrive more than once, and how to tell. */
14760
+ duplicates: TextSchema(10, 600),
14761
+ correlation: external_exports.array(LogCorrelationSchema).min(1).max(MAX_LOG_CORRELATION_FIELDS).optional(),
14762
+ notes: external_exports.array(TextSchema(10, 400)).min(1).max(MAX_LOG_INTERPRETATION_NOTES).optional()
14763
+ }).strict();
14764
+ var LogSemanticsDescriptorObjectSchema = external_exports.object({
14765
+ format: external_exports.literal(LOG_SEMANTICS_FORMAT_V1),
14766
+ fields: external_exports.array(LogFieldSchema).min(1).max(MAX_LOG_SEMANTICS_FIELDS),
14767
+ events: external_exports.array(LogEventSchema).min(1).max(MAX_LOG_SEMANTICS_EVENTS),
14768
+ interpretation: LogInterpretationSchema
14769
+ }).strict();
14770
+ var UNDECLARABLE = /* @__PURE__ */ new Set([
14771
+ ...RESERVED_RECORD_FIELDS,
14772
+ LOG_EVENT_FIELD,
14773
+ LOG_SCHEMA_REF_FIELD
14774
+ ]);
14775
+ function duplicates(values) {
14776
+ const seen = /* @__PURE__ */ new Set();
14777
+ const repeated = /* @__PURE__ */ new Set();
14778
+ for (const value of values)
14779
+ (seen.has(value) ? repeated : seen).add(value);
14780
+ return [...repeated];
14781
+ }
14782
+ var LogSemanticsDescriptorSchema = LogSemanticsDescriptorObjectSchema.superRefine((descriptor, ctx) => {
14783
+ const issue = (path, message) => ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path, message });
14784
+ const declared = new Set(descriptor.fields.map((field) => field.name));
14785
+ for (const name of duplicates(descriptor.fields.map((field) => field.name))) {
14786
+ issue(["fields"], `Field "${name}" is declared more than once.`);
14787
+ }
14788
+ for (const name of duplicates(descriptor.events.map((event) => event.name))) {
14789
+ issue(["events"], `Event "${name}" is declared more than once.`);
14790
+ }
14791
+ descriptor.fields.forEach((field, i) => {
14792
+ if (UNDECLARABLE.has(field.name)) {
14793
+ issue(["fields", i, "name"], `"${field.name}" is the record's own key or a convention field, and is not declared.`);
14794
+ }
14795
+ if (field.unit !== void 0 && field.type !== "number") {
14796
+ issue(["fields", i, "unit"], "Only a number field has a unit.");
14797
+ }
14798
+ if (field.values !== void 0 && field.type !== "string") {
14799
+ issue(["fields", i, "values"], "Only a string field lists values.");
14800
+ }
14801
+ if (field.closed !== void 0 && field.values === void 0) {
14802
+ issue(["fields", i, "closed"], "A closed field lists its values.");
14803
+ }
14804
+ for (const value of duplicates((field.values ?? []).map((entry) => entry.value))) {
14805
+ issue(["fields", i, "values"], `Value "${value}" is explained more than once.`);
14806
+ }
14807
+ });
14808
+ descriptor.events.forEach((event, i) => {
14809
+ for (const name of duplicates(event.fields.map((entry) => entry.field))) {
14810
+ issue(["events", i, "fields"], `Event "${event.name}" lists "${name}" more than once.`);
14811
+ }
14812
+ event.fields.forEach((entry, j) => {
14813
+ if (!declared.has(entry.field)) {
14814
+ issue(["events", i, "fields", j, "field"], `Event "${event.name}" names "${entry.field}", which is not a declared field.`);
14815
+ }
14816
+ });
14817
+ });
14818
+ const correlation = descriptor.interpretation.correlation ?? [];
14819
+ for (const name of duplicates(correlation.map((entry) => entry.field))) {
14820
+ issue(["interpretation", "correlation"], `Correlation field "${name}" is listed twice.`);
14821
+ }
14822
+ correlation.forEach((entry, i) => {
14823
+ if (!declared.has(entry.field)) {
14824
+ issue(["interpretation", "correlation", i, "field"], `"${entry.field}" is not a declared field.`);
14825
+ }
14826
+ });
14827
+ const bytes = logSemanticsByteLength(descriptor);
14828
+ if (bytes > MAX_LOG_SEMANTICS_BYTES) {
14829
+ issue([], `The descriptor is ${bytes} bytes as canonical JSON, over the ${MAX_LOG_SEMANTICS_BYTES}-byte bound.`);
14830
+ }
14831
+ });
14832
+ function logSemanticsByteLength(descriptor) {
14833
+ return new TextEncoder().encode(canonicalJson(descriptor)).length;
14834
+ }
14835
+
14325
14836
  // ../product-configuration/dist/manifest-yaml.js
14326
14837
  var import_yaml = __toESM(require_dist(), 1);
14327
14838
  function manifestYaml(document) {
14328
14839
  return (0, import_yaml.stringify)(document, { compat: "yaml-1.1" });
14329
14840
  }
14330
14841
 
14842
+ // ../platform-schemas/dist/governance-feed.js
14843
+ var GOVERNANCE_FEED_CATEGORIES = ["connections", "resources"];
14844
+ var OPAQUE_ID = /^[a-z]{2,4}_[0-9a-z]{10,40}$/;
14845
+ var opaqueId = external_exports.string().regex(OPAQUE_ID);
14846
+ var GovernanceFeedActorSchema = external_exports.discriminatedUnion("type", [
14847
+ external_exports.object({ type: external_exports.literal("person"), ref: external_exports.string().min(1).max(128) }),
14848
+ external_exports.object({ type: external_exports.literal("platform") }),
14849
+ external_exports.object({ type: external_exports.literal("resource"), id: opaqueId }),
14850
+ external_exports.object({ type: external_exports.literal("system") })
14851
+ ]);
14852
+ var connection = (action, description, outcome = "success") => ({
14853
+ category: "connections",
14854
+ action,
14855
+ outcome,
14856
+ target: "service_binding",
14857
+ description
14858
+ });
14859
+ var resource = (action, description, outcome = "success") => ({
14860
+ category: "resources",
14861
+ action,
14862
+ outcome,
14863
+ target: "resource",
14864
+ description
14865
+ });
14866
+ var GOVERNANCE_FEED_RULES = Object.freeze({
14867
+ // The portal's own connection.
14868
+ "service_binding.platform.connected": connection("connect", "The organization portal was connected to its log workspace."),
14869
+ "service_binding.platform.disconnected": connection("disconnect", "The organization portal was disconnected from its log workspace."),
14870
+ "service_binding.platform.rotation_requested": connection("rotate", "A new credential was requested for the portal\u2019s connection to its log workspace."),
14871
+ "service_binding.platform.ready": connection("complete", "The log workspace completed the portal\u2019s connection; records can be delivered."),
14872
+ "service_binding.platform.rotated": connection("rotate_complete", "The log workspace issued the portal\u2019s connection a new credential."),
14873
+ "service_binding.platform.failed": connection("complete", "The log workspace could not complete the portal\u2019s connection.", "failure"),
14874
+ "service_binding.platform.rotation_failed": connection("rotate_complete", "The log workspace could not issue the portal\u2019s connection a new credential.", "failure"),
14875
+ "service_binding.platform.released": connection("release", "The log workspace finished removing the portal\u2019s connection."),
14876
+ // Connections between Resources.
14877
+ "service_binding.disconnected": connection("disconnect", "A connection between two Resources was disconnected."),
14878
+ "service_binding.rotation_requested": connection("rotate", "A new credential was requested for a connection between two Resources."),
14879
+ "service_binding.ready": connection("complete", "The providing Resource completed a connection; the consuming Resource can use it."),
14880
+ "service_binding.rotated": connection("rotate_complete", "The providing Resource issued a connection a new credential."),
14881
+ "service_binding.failed": connection("complete", "The providing Resource could not complete a connection.", "failure"),
14882
+ "service_binding.rotation_failed": connection("rotate_complete", "The providing Resource could not issue a connection a new credential.", "failure"),
14883
+ "service_binding.released": connection("release", "The providing Resource finished removing a connection."),
14884
+ // How an operation on a Resource ended.
14885
+ "product.instance.active": resource("from-operation-type", "An operation on a Resource completed and the Resource is running; action names the operation."),
14886
+ "product.instance.suspended": resource("suspend", "A Resource was suspended."),
14887
+ "product.instance.deleted": resource("delete", "A Resource was deleted."),
14888
+ "product.instance.reconcile.failed": resource("reconcile", "An operation on a Resource failed and the platform stopped retrying it.", "failure"),
14889
+ "product.instance.upgrade.hop_committed": resource("upgrade", "A Resource finished moving to a new version."),
14890
+ "product.instance.upgrade.abandoned": resource("upgrade", "A Resource\u2019s move to a new version was abandoned; it stays on the version it had.", "failure")
14891
+ });
14892
+ var GOVERNANCE_FEED_EVENT_TYPES = Object.freeze(Object.keys(GOVERNANCE_FEED_RULES).sort());
14893
+ var GOVERNANCE_FEED_OPERATION_ACTIONS = Object.freeze({
14894
+ PRODUCT_INSTANCE_CREATE: "create",
14895
+ PRODUCT_INSTANCE_UPDATE: "configure",
14896
+ PRODUCT_INSTANCE_UPGRADE: "upgrade",
14897
+ PRODUCT_INSTANCE_SUSPEND: "suspend",
14898
+ PRODUCT_INSTANCE_RESUME: "resume",
14899
+ PRODUCT_INSTANCE_DELETE: "delete",
14900
+ PRODUCT_INSTANCE_RECONCILE: "reconcile"
14901
+ });
14902
+ var GovernanceFeedEventSchema = external_exports.object({
14903
+ /** The audit row's id. Stable, so a workspace can recognise a re-delivery. */
14904
+ id: opaqueId,
14905
+ /** The Control Plane's own event name, from its allowlist. */
14906
+ event: external_exports.string().regex(/^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*){1,4}$/).max(80),
14907
+ category: external_exports.enum(GOVERNANCE_FEED_CATEGORIES),
14908
+ /** RFC 3339: when it happened. */
14909
+ occurredAt: external_exports.string().datetime({ offset: true }),
14910
+ actor: GovernanceFeedActorSchema,
14911
+ action: external_exports.string().regex(/^[a-z][a-z0-9_]{0,31}$/),
14912
+ outcome: external_exports.enum(["success", "failure"]),
14913
+ projectId: opaqueId.optional(),
14914
+ /** The Resource the event is about — for a connection, the PROVIDER. */
14915
+ resourceId: opaqueId.optional(),
14916
+ /** For a connection between two Resources, the one that consumes it. */
14917
+ consumerResourceId: opaqueId.optional(),
14918
+ operationId: opaqueId.optional(),
14919
+ target: external_exports.object({
14920
+ type: external_exports.enum(["service_binding", "resource"]),
14921
+ id: opaqueId.optional()
14922
+ })
14923
+ });
14924
+ var GovernanceFeedPositionSchema = external_exports.object({
14925
+ at: external_exports.string().datetime({ offset: true }),
14926
+ id: external_exports.union([external_exports.literal(""), opaqueId])
14927
+ });
14928
+ var GOVERNANCE_FEED_MAX_LIMIT = 100;
14929
+ var GovernanceFeedRequestSchema = external_exports.object({
14930
+ after: GovernanceFeedPositionSchema,
14931
+ limit: external_exports.number().int().min(1).max(GOVERNANCE_FEED_MAX_LIMIT).optional()
14932
+ }).strict();
14933
+ var GovernanceFeedResponseSchema = external_exports.object({
14934
+ events: external_exports.array(GovernanceFeedEventSchema).max(GOVERNANCE_FEED_MAX_LIMIT),
14935
+ position: GovernanceFeedPositionSchema
14936
+ });
14937
+
14331
14938
  // ../platform-schemas/dist/index.js
14332
14939
  var TENANT_KEY_REGEX = /^[a-z][a-z0-9-]{2,23}[a-z0-9]$/;
14333
14940
  var RESERVED_TENANT_KEYS = /* @__PURE__ */ new Set([
@@ -14453,7 +15060,7 @@ var kindSchema = (kind) => external_exports.literal(kind, {
14453
15060
  errorMap: () => ({ message: `kind must be "${kind}".` })
14454
15061
  });
14455
15062
 
14456
- // ../product-contracts/dist/blocked-hosts.js
15063
+ // ../public-fetch/dist/index.js
14457
15064
  var BLOCKED_HOSTS = [
14458
15065
  /^localhost$/i,
14459
15066
  /^169\.254\./,
@@ -14686,6 +15293,7 @@ var IDENTITY_OIDC_CLIENT_V1 = {
14686
15293
  }
14687
15294
  };
14688
15295
  var DIRECTORY_SCOPES = [
15296
+ "directory.group-members.list",
14689
15297
  "directory.groups.search",
14690
15298
  "directory.membership.check",
14691
15299
  "directory.principals.resolve",
@@ -14737,9 +15345,58 @@ var IDENTITY_DIRECTORY_V1 = {
14737
15345
  }
14738
15346
  }
14739
15347
  };
15348
+ var OBSERVABILITY_LOGS_SCOPES = ["logs.read", "logs.write"];
15349
+ var OBSERVABILITY_LOGS_V1 = {
15350
+ kind: "service-api",
15351
+ id: "observability.logs",
15352
+ version: "v1",
15353
+ // TENANT-MULTIPLE, for the reason the two identity interfaces are: an
15354
+ // organization may run more than one log workspace — one per environment, one
15355
+ // per department — and which one a Product emits to is the customer's answer
15356
+ // rather than the platform's. There is deliberately no fallback to "the only
15357
+ // one" when a Tenant happens to have exactly one, because that rule would
15358
+ // change the day a second workspace is deployed and bind existing consumers
15359
+ // by accident.
15360
+ cardinality: "tenant-multiple",
15361
+ summary: "Send operational log records to a log workspace in this organization, and optionally query them back. The provider returns an ingest address and a credential; the consumer calls it directly.",
15362
+ maxRequestedScopes: OBSERVABILITY_LOGS_SCOPES.length,
15363
+ scopes: OBSERVABILITY_LOGS_SCOPES,
15364
+ // `logs.write` IS REQUIRED, and it is the only required scope on any
15365
+ // interface in this file. The reason is that the alternative is incoherent
15366
+ // rather than merely narrow: a binding to a log workspace that may not write
15367
+ // to it has asked for a connection it cannot use for the thing the connection
15368
+ // is for. A consumer that only wants to READ somebody else's records is not
15369
+ // this interface's case — it is a query surface the providing Product
15370
+ // exposes in its own right.
15371
+ requiredScopes: ["logs.write"],
15372
+ protocol: {
15373
+ authScheme: "bearer",
15374
+ credentialTransport: "authorization-header"
15375
+ },
15376
+ fields: {
15377
+ ingestUrl: {
15378
+ kind: "attribute",
15379
+ always: true,
15380
+ format: "https-url",
15381
+ description: 'The base address records are sent to: `POST {ingestUrl}/records` with `{"records": [...]}`, each record `{message, severity, observedAt?, fields?}`. Operations are relative to it, so the provider can move them without a new binding.'
15382
+ },
15383
+ grantedScopes: {
15384
+ kind: "attribute",
15385
+ always: true,
15386
+ format: "space-separated-tokens",
15387
+ description: "Space-separated scopes actually granted, which may be narrower than those requested. Read this rather than assuming the request was honoured."
15388
+ },
15389
+ apiToken: {
15390
+ kind: "secret",
15391
+ always: true,
15392
+ description: "The credential for the address above, presented as a bearer token. Delivered as a secret reference, never as an environment value, and rotatable without a new binding."
15393
+ }
15394
+ }
15395
+ };
14740
15396
  var SERVICE_INTERFACE_CONTRACTS = {
14741
15397
  "identity.oidc-client/v1": IDENTITY_OIDC_CLIENT_V1,
14742
- "identity.directory/v1": IDENTITY_DIRECTORY_V1
15398
+ "identity.directory/v1": IDENTITY_DIRECTORY_V1,
15399
+ "observability.logs/v1": OBSERVABILITY_LOGS_V1
14743
15400
  };
14744
15401
  var SERVICE_INTERFACE_KEYS = Object.keys(SERVICE_INTERFACE_CONTRACTS).sort();
14745
15402
  var SERVICE_INTERFACE_IDS = [
@@ -14998,11 +15655,35 @@ var ProductDependencySchema = external_exports.object({
14998
15655
  var ProductDependencyV2Schema = ProductDependencySchema.extend({
14999
15656
  requiredWhen: RequiredWhenV2Schema.optional()
15000
15657
  });
15658
+ function hasControlCharacter2(value) {
15659
+ for (let i = 0; i < value.length; i += 1) {
15660
+ const code = value.charCodeAt(i);
15661
+ if (code < 32 || code === 127)
15662
+ return true;
15663
+ }
15664
+ return false;
15665
+ }
15666
+ var GuidanceTextSchema = (min, max) => external_exports.string().min(min).max(max).refine((value) => !hasControlCharacter2(value), {
15667
+ message: "Guidance is one line, with no control characters."
15668
+ });
15669
+ var ConsumerGuidanceSchema = external_exports.object({
15670
+ /** One paragraph: what this provider does with what a consumer sends or asks. */
15671
+ summary: GuidanceTextSchema(20, 600),
15672
+ /** Concrete, provider-specific practices, one per entry. */
15673
+ notes: external_exports.array(GuidanceTextSchema(10, 400)).min(1).max(12).optional()
15674
+ }).strict();
15001
15675
  var ServiceInterfaceProvideSchema = external_exports.object({
15002
15676
  key: ServiceBindingKeySchema,
15003
15677
  interfaceId: ServiceInterfaceIdSchema,
15004
15678
  interfaceVersion: ServiceInterfaceVersionSchema,
15005
- cardinality: ServiceInterfaceCardinalitySchema
15679
+ cardinality: ServiceInterfaceCardinalitySchema,
15680
+ /**
15681
+ * `.optional()`, NEVER `.default()`. A default materialises the key,
15682
+ * `contractDigest` digests the parsed object, and every release published
15683
+ * before this field existed would be refused on republish as
15684
+ * `RELEASED_CONTRACT_MUTATED`.
15685
+ */
15686
+ consumerGuidance: ConsumerGuidanceSchema.optional()
15006
15687
  }).strict();
15007
15688
  var LifecycleCapabilitiesSchema = external_exports.object({
15008
15689
  upgrade: external_exports.boolean(),
@@ -15133,6 +15814,17 @@ var ProductDefinitionObjectSchema = external_exports.object({
15133
15814
  * Read absence through the empty array at the use site instead.
15134
15815
  */
15135
15816
  serviceInterfaces: external_exports.object({ provides: external_exports.array(ServiceInterfaceProvideSchema).min(1).max(4) }).strict().optional(),
15817
+ /**
15818
+ * What this Product's own log records mean — see `log-semantics.ts` in `product-configuration`.
15819
+ *
15820
+ * `.optional()`, NEVER `.default()`. A default materialises the key,
15821
+ * `contractDigest` digests the parsed object, and every release published
15822
+ * before this field existed would be refused on republish as
15823
+ * `RELEASED_CONTRACT_MUTATED`. A NEW release that sends records to
15824
+ * `observability.logs/v1` must declare it (`validatePublishableDefinition`);
15825
+ * a record never needs it to be accepted.
15826
+ */
15827
+ observability: external_exports.object({ logs: LogSemanticsDescriptorSchema }).strict().optional(),
15136
15828
  lifecycle: LifecycleCapabilitiesSchema,
15137
15829
  policies: PolicyReferencesSchema,
15138
15830
  /**
@@ -16062,10 +16754,38 @@ var ComponentHealthSchema = external_exports.object({
16062
16754
  readiness: ProbeSchema,
16063
16755
  liveness: ProbeSchema
16064
16756
  }).strict();
16757
+ var PUBLIC_INGRESS_AUTHENTICATION = external_exports.enum([
16758
+ "api-key",
16759
+ "oauth2",
16760
+ "mtls",
16761
+ "signed-request",
16762
+ "tenant-human-session"
16763
+ ]);
16764
+ var HUMAN_SESSION_MECHANISMS = external_exports.array(external_exports.enum(["federated-oidc", "email-one-time-link", "local-password"])).min(1).max(3);
16765
+ var INGRESS_PATH_PREFIX = external_exports.string().regex(/^\/[a-z0-9\-/]*$/).max(64);
16766
+ var IngressSurfaceSchema = external_exports.object({
16767
+ pathPrefix: INGRESS_PATH_PREFIX,
16768
+ authentication: PUBLIC_INGRESS_AUTHENTICATION,
16769
+ /** Required and refused exactly as the component-level field is. */
16770
+ humanSessionMechanisms: HUMAN_SESSION_MECHANISMS.optional(),
16771
+ /** Who calls this part of the component, for the reviewer. */
16772
+ purpose: external_exports.string().min(20).max(300)
16773
+ }).strict();
16774
+ var MAX_INGRESS_SURFACES = 8;
16065
16775
  var ComponentIngressSchema = external_exports.discriminatedUnion("exposure", [
16066
16776
  external_exports.object({
16067
16777
  exposure: external_exports.literal("public"),
16068
16778
  /**
16779
+ * `oauth2`: the routes it names accept OAuth access tokens. The
16780
+ * Authorization Server that issues them may be an external authority, or
16781
+ * the Product may implement it itself — an MCP server that runs its own
16782
+ * OAuth 2.1 discovery, authorization, consent, PKCE and token issuance
16783
+ * declares `oauth2` for the endpoint those tokens reach. Either way
16784
+ * the Product must truthfully implement and expose the flow it declares.
16785
+ * The platform provides none of it implicitly: it runs no Authorization
16786
+ * Server, registers no client, issues no token and validates none in
16787
+ * front of the container.
16788
+ *
16069
16789
  * `tenant-human-session` is the one that needs explaining: a console the
16070
16790
  * Product itself signs humans into, rather than delegating to a caller's
16071
16791
  * credential.
@@ -16086,16 +16806,18 @@ var ComponentIngressSchema = external_exports.discriminatedUnion("exposure", [
16086
16806
  * defaulted to, it runs with mail disabled entirely, and the temporary
16087
16807
  * credential it bootstraps has its own list of properties in that section.
16088
16808
  *
16089
- * `oauth2` remains correct for a component that accepts a token somebody
16090
- * else minted. Declare this one when the Product owns the session.
16809
+ * WHERE THE TWO MEET: a Product that is its own Authorization Server. The
16810
+ * person approving a client must first be signed in through the Product's
16811
+ * ordinary `tenant-human-session` — approving is something a signed-in
16812
+ * person does, never a sign-in of its own — so the routes serving that
16813
+ * sign-in and the approval declare `tenant-human-session` and its
16814
+ * mechanisms, as the component's own `authentication` or as a surface,
16815
+ * and the routes accepting the resulting tokens declare `oauth2`. `oauth2`
16816
+ * describes how a token is PRESENTED, never how a person signs in:
16817
+ * whoever issued the token, a sign-in the Product runs carries the
16818
+ * obligation above.
16091
16819
  */
16092
- authentication: external_exports.enum([
16093
- "api-key",
16094
- "oauth2",
16095
- "mtls",
16096
- "signed-request",
16097
- "tenant-human-session"
16098
- ]),
16820
+ authentication: PUBLIC_INGRESS_AUTHENTICATION,
16099
16821
  /**
16100
16822
  * WHICH of those three mechanisms this Product actually offers.
16101
16823
  *
@@ -16118,8 +16840,16 @@ var ComponentIngressSchema = external_exports.discriminatedUnion("exposure", [
16118
16840
  * which — never a failure of another. Listing all three means all three
16119
16841
  * are implemented, not that the Product will fall back through them.
16120
16842
  */
16121
- humanSessionMechanisms: external_exports.array(external_exports.enum(["federated-oidc", "email-one-time-link", "local-password"])).min(1).max(3).optional(),
16122
- pathPrefix: external_exports.string().regex(/^\/[a-z0-9\-/]*$/).max(64),
16843
+ humanSessionMechanisms: HUMAN_SESSION_MECHANISMS.optional(),
16844
+ pathPrefix: INGRESS_PATH_PREFIX,
16845
+ /**
16846
+ * Parts of this component's routes that authenticate differently from
16847
+ * `authentication` above. Optional, and `.optional()` rather than a
16848
+ * default: a released Blueprint is re-parsed on the read path and its
16849
+ * digest is taken over the parsed object, so a materialised empty list
16850
+ * would refuse a republish of unchanged bytes.
16851
+ */
16852
+ surfaces: external_exports.array(IngressSurfaceSchema).min(1).max(MAX_INGRESS_SURFACES).optional(),
16123
16853
  /**
16124
16854
  * Why this component is public at all. docs/SECURITY.md §8 makes adding
16125
16855
  * public ingress a security-approval trigger; requiring the author to
@@ -16153,7 +16883,15 @@ var InstanceEnvBindingSchema = external_exports.object({
16153
16883
  "productVersion",
16154
16884
  "profile",
16155
16885
  "region",
16156
- "environment"
16886
+ "environment",
16887
+ /**
16888
+ * The reference of the log-semantics descriptor this release's
16889
+ * Definition declares — the value a record carries in
16890
+ * `vfac_log_schema`. Resolved from the release the component is
16891
+ * deployed from, so during an upgrade each container carries its own
16892
+ * release's. A Definition declaring no descriptor cannot bind it.
16893
+ */
16894
+ "logSemanticsRef"
16157
16895
  ])
16158
16896
  }).strict();
16159
16897
  var PlatformEnvBindingSchema = external_exports.object({
@@ -16678,6 +17416,20 @@ var DataStoreSchema = external_exports.object({
16678
17416
  * or push it outside the contract — where nothing governs its retention,
16679
17417
  * its data categories or its deletion.
16680
17418
  *
17419
+ * `log-store` was added for the same reason one level along: a Product that
17420
+ * emits structured operational records about its own running — requests
17421
+ * served, durations, outcomes — had nowhere in this contract to say so, and
17422
+ * a store it cannot name is a store whose retention, data categories and
17423
+ * deletion nothing governs. It is LOGS, deliberately and only. Metrics and
17424
+ * traces are different capabilities with different shapes, and naming this
17425
+ * one after the signal it carries is what keeps a later `metric-store` from
17426
+ * being a silent widening of this one.
17427
+ *
17428
+ * PROVIDER-NEUTRAL, like every other name here. Nothing in this enum names
17429
+ * an engine, a vendor or a wire format, and `log-store` must not become the
17430
+ * exception — which implementation backs it is a platform promise published
17431
+ * in `DATA_STORE_INTERFACES`, not a choice a publisher makes.
17432
+ *
16681
17433
  * Naming a kind is a CONTRACT question. Whether this Control Plane can
16682
17434
  * provision it is a DEPLOYABILITY question, answered separately by
16683
17435
  * `validateDeployability` — see deployability.ts.
@@ -16689,7 +17441,8 @@ var DataStoreSchema = external_exports.object({
16689
17441
  "vector-index",
16690
17442
  "queue",
16691
17443
  "document-database",
16692
- "search-index"
17444
+ "search-index",
17445
+ "log-store"
16693
17446
  ]),
16694
17447
  purpose: external_exports.string().min(10).max(300),
16695
17448
  /**
@@ -16711,7 +17464,7 @@ var DataStoreSchema = external_exports.object({
16711
17464
  }).strict(),
16712
17465
  deleteBehavior: external_exports.enum(["purge-with-instance", "purge-after-retention"]),
16713
17466
  /**
16714
- * Ask for RUNTIME access to the store's own objects.
17467
+ * Ask for RUNTIME access to the store's own contents.
16715
17468
  *
16716
17469
  * OPT-IN, AND PRESENT-OR-ABSENT RATHER THAN AN ENUM. A `mode` field would
16717
17470
  * have to be optional with a default to keep published documents parseable,
@@ -16720,17 +17473,31 @@ var DataStoreSchema = external_exports.object({
16720
17473
  * and a legitimate republish would be refused as an immutability conflict.
16721
17474
  * Presence is a cleaner discriminator and reads better in a diff.
16722
17475
  *
16723
- * WHAT IT DOES NOT GRANT: S3 credentials. The component receives an address,
16724
- * its own key prefix and a signing key for the platform's storage broker,
16725
- * which checks every key against the INSTANCE the signature proved. A
16726
- * Scaleway key cannot be scoped to a prefix, and the provider's quota is 50
16727
- * API keys per Organization, so one per Product Instance would cap the
16728
- * platform at roughly 45 of them.
17476
+ * WHAT IT DOES NOT GRANT: a provider credential, on either kind that
17477
+ * accepts it. The component receives an address, what identifies it, and a
17478
+ * signing key for the platform service that brokers the access — and that
17479
+ * service checks every request against the INSTANCE the signature proved.
17480
+ *
17481
+ * For `object-storage` the reason is that a Scaleway key cannot be scoped
17482
+ * to a prefix or a bucket, and the provider's quota is 50 API keys per
17483
+ * Organization, so one per Product Instance would cap the platform at
17484
+ * roughly 45 of them. For `log-store` the reason is sharper: a Cockpit
17485
+ * token carries a Project and a scope and has NO data-source field, and the
17486
+ * whole platform lives in one Scaleway Project — so a token delivered to
17487
+ * one Tenant's component would read and write every other Tenant's logs.
17488
+ * Neither is a limit to work around; both are why the brokering service
17489
+ * exists.
17490
+ *
17491
+ * WHICH KINDS MAY DECLARE IT is `RUNTIME_ACCESS_KINDS`, and the variables
17492
+ * each one then receives are `RUNTIME_ACCESS_SUFFIXES`. The cross-field
17493
+ * check below reads both.
16729
17494
  */
16730
17495
  runtimeAccess: external_exports.object({
16731
17496
  /**
16732
- * Names the variables the platform injects — `{PREFIX}_ENDPOINT_URL`,
16733
- * `_TENANT_ID`, `_PREFIX` and `_SIGNING_KEY`.
17497
+ * Names the variables the platform injects: `{PREFIX}_ENDPOINT_URL`,
17498
+ * `_TENANT_ID` and `_SIGNING_KEY` on every kind that accepts runtime
17499
+ * access, plus `_PREFIX` on `object-storage`, which is the only one
17500
+ * addressed by a key prefix.
16734
17501
  *
16735
17502
  * Bounded at 32 so every derived name fits `EnvVarNameSchema`'s 64, and
16736
17503
  * REFUSED at authoring time rather than silently truncated at deploy —
@@ -16749,6 +17516,17 @@ var DataStoreSchema = external_exports.object({
16749
17516
  access: external_exports.enum(["read-write", "read-only"])
16750
17517
  }).strict()).min(1).max(12)
16751
17518
  }).strict();
17519
+ var RUNTIME_ACCESS_SUFFIXES = {
17520
+ "object-storage": ["_ENDPOINT_URL", "_TENANT_ID", "_PREFIX", "_SIGNING_KEY"],
17521
+ "log-store": ["_ENDPOINT_URL", "_TENANT_ID", "_SIGNING_KEY"]
17522
+ };
17523
+ var RUNTIME_ACCESS_KINDS = new Set(Object.keys(RUNTIME_ACCESS_SUFFIXES));
17524
+ function runtimeAccessEnvNames(kind, envPrefix) {
17525
+ const suffixes = RUNTIME_ACCESS_SUFFIXES[kind];
17526
+ if (suffixes === void 0)
17527
+ return [];
17528
+ return suffixes.map((suffix) => `${envPrefix}${suffix}`);
17529
+ }
16752
17530
  var EgressRuleSchema = external_exports.object({
16753
17531
  componentId: ComponentIdSchema,
16754
17532
  destination: external_exports.discriminatedUnion("kind", [
@@ -17155,7 +17933,7 @@ function checkDeploymentBlueprint(blueprint, ctx) {
17155
17933
  message: `Artifact source "${id}" is declared more than once.`
17156
17934
  });
17157
17935
  });
17158
- const duplicateTypes = (blueprint.dynamicResources ?? []).map((resource) => resource.type).filter((type, index, all) => all.indexOf(type) !== index);
17936
+ const duplicateTypes = (blueprint.dynamicResources ?? []).map((resource2) => resource2.type).filter((type, index, all) => all.indexOf(type) !== index);
17159
17937
  [...new Set(duplicateTypes)].forEach((type) => {
17160
17938
  ctx.addIssue({
17161
17939
  code: external_exports.ZodIssueCode.custom,
@@ -17163,15 +17941,15 @@ function checkDeploymentBlueprint(blueprint, ctx) {
17163
17941
  message: `"${type}" is declared more than once. A resource type may appear at most once: everything that reads this list resolves a capability by its type.`
17164
17942
  });
17165
17943
  });
17166
- (blueprint.dynamicResources ?? []).forEach((resource, index) => {
17167
- if (!sourceIds.has(resource.bootstrap.artifactSource)) {
17944
+ (blueprint.dynamicResources ?? []).forEach((resource2, index) => {
17945
+ if (!sourceIds.has(resource2.bootstrap.artifactSource)) {
17168
17946
  ctx.addIssue({
17169
17947
  code: external_exports.ZodIssueCode.custom,
17170
17948
  path: ["dynamicResources", index, "bootstrap", "artifactSource"],
17171
- message: `No artifact source "${resource.bootstrap.artifactSource}" is declared. Declare it in "artifactSources" first.`
17949
+ message: `No artifact source "${resource2.bootstrap.artifactSource}" is declared. Declare it in "artifactSources" first.`
17172
17950
  });
17173
17951
  }
17174
- resource.consumers.forEach((consumerId, position) => {
17952
+ resource2.consumers.forEach((consumerId, position) => {
17175
17953
  if (!knownComponents.has(consumerId)) {
17176
17954
  ctx.addIssue({
17177
17955
  code: external_exports.ZodIssueCode.custom,
@@ -17189,7 +17967,7 @@ function checkDeploymentBlueprint(blueprint, ctx) {
17189
17967
  });
17190
17968
  }
17191
17969
  });
17192
- const handoff = resource.bootstrap.productHandoff;
17970
+ const handoff = resource2.bootstrap.productHandoff;
17193
17971
  if (handoff !== void 0) {
17194
17972
  const handoffPath = ["dynamicResources", index, "bootstrap", "productHandoff", "componentId"];
17195
17973
  const target = blueprint.components.find((component) => component.id === handoff.componentId);
@@ -17213,7 +17991,7 @@ function checkDeploymentBlueprint(blueprint, ctx) {
17213
17991
  message: `Component "${handoff.componentId}" declares "${target.ingress.exposure}" ingress. A Product handoff target must declare public ingress: the machine reaches it from outside the platform's network, which is the whole point of an outbound handoff.`
17214
17992
  });
17215
17993
  }
17216
- if (!resource.consumers.includes(handoff.componentId)) {
17994
+ if (!resource2.consumers.includes(handoff.componentId)) {
17217
17995
  ctx.addIssue({
17218
17996
  code: external_exports.ZodIssueCode.custom,
17219
17997
  path: handoffPath,
@@ -17244,29 +18022,34 @@ function checkDeploymentBlueprint(blueprint, ctx) {
17244
18022
  });
17245
18023
  blueprint.components.forEach((component, index) => {
17246
18024
  if (component.ingress.exposure === "public") {
17247
- const mechanismPath = ["components", index, "ingress", "humanSessionMechanisms"];
17248
- const declared = component.ingress.humanSessionMechanisms;
17249
- if (component.ingress.authentication === "tenant-human-session") {
17250
- if (declared === void 0) {
18025
+ const ingress = component.ingress;
18026
+ checkHumanSessionMechanisms(ctx, ["components", index, "ingress"], ingress.authentication, ingress.humanSessionMechanisms);
18027
+ const surfaces = ingress.surfaces ?? [];
18028
+ const base = ingress.pathPrefix.replace(/\/+$/, "");
18029
+ surfaces.forEach((surface, surfaceIndex) => {
18030
+ const surfacePath = ["components", index, "ingress", "surfaces", surfaceIndex];
18031
+ checkHumanSessionMechanisms(ctx, surfacePath, surface.authentication, surface.humanSessionMechanisms);
18032
+ const prefix = surface.pathPrefix.replace(/\/+$/, "");
18033
+ if (!(prefix.startsWith(`${base}/`) && prefix.length > base.length + 1)) {
17251
18034
  ctx.addIssue({
17252
18035
  code: external_exports.ZodIssueCode.custom,
17253
- path: mechanismPath,
17254
- message: "A component declaring `tenant-human-session` must name which approved mechanisms it implements, so a customer can tell whether it fits their organization before they deploy it."
17255
- });
17256
- } else if (new Set(declared).size !== declared.length) {
17257
- ctx.addIssue({
17258
- code: external_exports.ZodIssueCode.custom,
17259
- path: mechanismPath,
17260
- message: "Each mechanism may be named once."
18036
+ path: [...surfacePath, "pathPrefix"],
18037
+ message: `A surface covers part of the component's routes, so its prefix must lie under "${ingress.pathPrefix}" and differ from it.`
17261
18038
  });
17262
18039
  }
17263
- } else if (declared !== void 0) {
17264
- ctx.addIssue({
17265
- code: external_exports.ZodIssueCode.custom,
17266
- path: mechanismPath,
17267
- message: "Only a component whose ingress authentication is `tenant-human-session` signs people in itself; remove this, or declare that authentication."
18040
+ surfaces.forEach((other, otherIndex) => {
18041
+ if (otherIndex <= surfaceIndex)
18042
+ return;
18043
+ const otherPrefix = other.pathPrefix.replace(/\/+$/, "");
18044
+ if (otherPrefix === prefix || otherPrefix.startsWith(`${prefix}/`) || prefix.startsWith(`${otherPrefix}/`)) {
18045
+ ctx.addIssue({
18046
+ code: external_exports.ZodIssueCode.custom,
18047
+ path: ["components", index, "ingress", "surfaces", otherIndex, "pathPrefix"],
18048
+ message: `Surfaces "${surface.pathPrefix}" and "${other.pathPrefix}" overlap; a path may be claimed by one surface only.`
18049
+ });
18050
+ }
17268
18051
  });
17269
- }
18052
+ });
17270
18053
  }
17271
18054
  const envNames = component.env.map((binding) => binding.name);
17272
18055
  const duplicateEnv = envNames.filter((name, i) => envNames.indexOf(name) !== i);
@@ -17596,11 +18379,11 @@ function checkDeploymentBlueprint(blueprint, ctx) {
17596
18379
  if (!access)
17597
18380
  return;
17598
18381
  const path = ["dataStores", index, "runtimeAccess"];
17599
- if (store.kind !== "object-storage") {
18382
+ if (!RUNTIME_ACCESS_KINDS.has(store.kind)) {
17600
18383
  ctx.addIssue({
17601
18384
  code: external_exports.ZodIssueCode.custom,
17602
18385
  path,
17603
- message: `Runtime access is only available for an object-storage store; "${store.id}" is a ${store.kind}.`
18386
+ message: `Runtime access is only available for a ${[...RUNTIME_ACCESS_KINDS].sort().join(" or ")} store; "${store.id}" is a ${store.kind}.`
17604
18387
  });
17605
18388
  }
17606
18389
  const first = usedPrefixes.get(access.envPrefix);
@@ -17612,7 +18395,7 @@ function checkDeploymentBlueprint(blueprint, ctx) {
17612
18395
  });
17613
18396
  }
17614
18397
  usedPrefixes.set(access.envPrefix, index);
17615
- const derived = new Set(["_ENDPOINT_URL", "_TENANT_ID", "_PREFIX", "_SIGNING_KEY"].map((suffix) => `${access.envPrefix}${suffix}`));
18398
+ const derived = new Set(runtimeAccessEnvNames(store.kind, access.envPrefix));
17616
18399
  const binders = new Set(store.bindings.map((binding) => binding.componentId));
17617
18400
  for (const component of blueprint.components) {
17618
18401
  if (!binders.has(component.id))
@@ -17795,12 +18578,12 @@ function checkDeploymentBlueprint(blueprint, ctx) {
17795
18578
  message: `\`${edges.field}\` declares versions this Product ${edges.role}, but \`lifecycle.${edges.operation}.supported\` is false.`
17796
18579
  });
17797
18580
  }
17798
- const duplicates = versions.filter((version, i) => versions.indexOf(version) !== i);
17799
- if (duplicates.length > 0) {
18581
+ const duplicates2 = versions.filter((version, i) => versions.indexOf(version) !== i);
18582
+ if (duplicates2.length > 0) {
17800
18583
  ctx.addIssue({
17801
18584
  code: external_exports.ZodIssueCode.custom,
17802
18585
  path: [...edges.path],
17803
- message: `Duplicate versions: ${[...new Set(duplicates)].join(", ")}.`
18586
+ message: `Duplicate versions: ${[...new Set(duplicates2)].join(", ")}.`
17804
18587
  });
17805
18588
  }
17806
18589
  const notOlder = versions.filter((version) => compareSemver(version, blueprint.productVersion) >= 0);
@@ -17813,6 +18596,30 @@ function checkDeploymentBlueprint(blueprint, ctx) {
17813
18596
  }
17814
18597
  }
17815
18598
  }
18599
+ function checkHumanSessionMechanisms(ctx, at, authentication, declared) {
18600
+ const mechanismPath = [...at, "humanSessionMechanisms"];
18601
+ if (authentication === "tenant-human-session") {
18602
+ if (declared === void 0) {
18603
+ ctx.addIssue({
18604
+ code: external_exports.ZodIssueCode.custom,
18605
+ path: mechanismPath,
18606
+ message: "A component declaring `tenant-human-session` must name which approved mechanisms it implements, so a customer can tell whether it fits their organization before they deploy it."
18607
+ });
18608
+ } else if (new Set(declared).size !== declared.length) {
18609
+ ctx.addIssue({
18610
+ code: external_exports.ZodIssueCode.custom,
18611
+ path: mechanismPath,
18612
+ message: "Each mechanism may be named once."
18613
+ });
18614
+ }
18615
+ } else if (declared !== void 0) {
18616
+ ctx.addIssue({
18617
+ code: external_exports.ZodIssueCode.custom,
18618
+ path: mechanismPath,
18619
+ message: "Only a component whose ingress authentication is `tenant-human-session` signs people in itself; remove this, or declare that authentication."
18620
+ });
18621
+ }
18622
+ }
17816
18623
 
17817
18624
  // ../product-contracts/dist/deployability.js
17818
18625
  var VIRTUAL_MACHINE_PROFILE_INTERFACE = {
@@ -17979,6 +18786,46 @@ var PUBLISH_RULE_ANNEX = {
17979
18786
  }
17980
18787
  }
17981
18788
  }
18789
+ },
18790
+ {
18791
+ // THE SAME ASYMMETRY, for what a producer's log records mean — and only
18792
+ // for a producer: the rule is conditional on a dependency on
18793
+ // `observability.logs/v1`, the one declaration that means "sends log
18794
+ // records". Presence is all JSON Schema says; the descriptor's own rules
18795
+ // are in its schema.
18796
+ $comment: "A new release that sends records to observability.logs/v1 (a dependency on that interface) must declare definition.observability.logs: the events it produces and the fields they carry. A log record is never refused for lacking one.",
18797
+ if: {
18798
+ required: ["definition"],
18799
+ properties: {
18800
+ definition: {
18801
+ required: ["dependencies"],
18802
+ properties: {
18803
+ dependencies: {
18804
+ contains: {
18805
+ required: ["interface"],
18806
+ properties: {
18807
+ interface: {
18808
+ required: ["id", "version"],
18809
+ properties: {
18810
+ id: { const: "observability.logs" },
18811
+ version: { const: "v1" }
18812
+ }
18813
+ }
18814
+ }
18815
+ }
18816
+ }
18817
+ }
18818
+ }
18819
+ }
18820
+ },
18821
+ then: {
18822
+ properties: {
18823
+ definition: {
18824
+ required: ["observability"],
18825
+ properties: { observability: { required: ["logs"] } }
18826
+ }
18827
+ }
18828
+ }
17982
18829
  }
17983
18830
  ]
17984
18831
  };
@@ -18380,7 +19227,6 @@ function printPlan(plan, json) {
18380
19227
  }
18381
19228
  }
18382
19229
  }
18383
- lines.push(...requiredSecretLines(plan.requiredSecrets ?? []));
18384
19230
  if ((plan.secretsToWrite ?? []).length > 0) {
18385
19231
  lines.push(` secrets to set: ${plan.secretsToWrite.join(", ")}`);
18386
19232
  for (const line of secretWriteConsequence(plan)) lines.push(` ${line}`);
@@ -19164,21 +20010,21 @@ async function runGet(parsed, deps = {}) {
19164
20010
  `);
19165
20011
  return 0;
19166
20012
  }
19167
- const resource = result.data["resource"] ?? {};
20013
+ const resource2 = result.data["resource"] ?? {};
19168
20014
  const lines = [
19169
- `${String(resource["productInstanceId"] ?? instanceId)} ${String(resource["status"] ?? "")} ${String(resource["displayName"] ?? "")}`,
19170
- ` product ${String(resource["productId"] ?? "")} ${String(resource["productVersion"] ?? "")}`,
19171
- ` region ${String(resource["region"] ?? "")}`,
19172
- ` profile ${String(resource["profile"] ?? "")}`
20015
+ `${String(resource2["productInstanceId"] ?? instanceId)} ${String(resource2["status"] ?? "")} ${String(resource2["displayName"] ?? "")}`,
20016
+ ` product ${String(resource2["productId"] ?? "")} ${String(resource2["productVersion"] ?? "")}`,
20017
+ ` region ${String(resource2["region"] ?? "")}`,
20018
+ ` profile ${String(resource2["profile"] ?? "")}`
19173
20019
  ];
19174
- const key = resource["manifestKey"];
20020
+ const key = resource2["manifestKey"];
19175
20021
  if (typeof key === "string") lines.push(` key ${key}`);
19176
- const outputs = resource["outputs"] ?? {};
19177
- const declared = Array.isArray(resource["outputDeclarations"]) ? resource["outputDeclarations"] : [];
20022
+ const outputs = resource2["outputs"] ?? {};
20023
+ const declared = Array.isArray(resource2["outputDeclarations"]) ? resource2["outputDeclarations"] : [];
19178
20024
  const declaredNames = declared.map((entry) => entry["key"]).filter((key2) => typeof key2 === "string");
19179
20025
  const names = [.../* @__PURE__ */ new Set([...Object.keys(outputs), ...declaredNames])].sort();
19180
20026
  const isSecret2 = (name) => declared.find((entry) => entry["key"] === name)?.["secret"] === true;
19181
- const conditions = Array.isArray(resource["conditions"]) ? resource["conditions"] : [];
20027
+ const conditions = Array.isArray(resource2["conditions"]) ? resource2["conditions"] : [];
19182
20028
  if (conditions.length > 0) {
19183
20029
  lines.push("", " Operational");
19184
20030
  const width = Math.max(
@@ -19217,7 +20063,7 @@ async function runGet(parsed, deps = {}) {
19217
20063
  }
19218
20064
  }
19219
20065
  } else {
19220
- const status = String(resource["status"] ?? "");
20066
+ const status = String(resource2["status"] ?? "");
19221
20067
  lines.push(
19222
20068
  "",
19223
20069
  status === "DELETED" ? " No outputs. This resource was deleted." : status === "FAILED" ? " No outputs. Its deployment failed \u2014 `vfac get operation` says why." : " No outputs yet. A resource publishes them when provisioning resolves them."
@@ -19284,7 +20130,7 @@ This is one page. Continue with --cursor ${result.nextCursor ?? ""}
19284
20130
  process.stderr.write("No Project. Use --project prj_\u2026 or set one in your context.\n");
19285
20131
  return 2;
19286
20132
  }
19287
- const resource = parsed.flags["resource"];
20133
+ const resource2 = parsed.flags["resource"];
19288
20134
  const result = await readAllPages({
19289
20135
  endpoint: session.ready.endpoint,
19290
20136
  token: session.ready.token,
@@ -19293,7 +20139,7 @@ This is one page. Continue with --cursor ${result.nextCursor ?? ""}
19293
20139
  cursor: parsed.flags["cursor"],
19294
20140
  pageSize: parsed.flags["page-size"],
19295
20141
  onePage: true,
19296
- ...resource === void 0 ? {} : { query: { productInstanceId: resource } }
20142
+ ...resource2 === void 0 ? {} : { query: { productInstanceId: resource2 } }
19297
20143
  });
19298
20144
  if (!result.ok) {
19299
20145
  if (result.error.status === 404 && result.error.code === "NOT_FOUND") {
@@ -19313,7 +20159,7 @@ This is one page. Continue with --cursor ${result.nextCursor ?? ""}
19313
20159
  }
19314
20160
  if (result.items.length === 0) {
19315
20161
  process.stdout.write(
19316
- resource === void 0 ? "No operations in this project.\n" : "No operations for this resource in this project.\n"
20162
+ resource2 === void 0 ? "No operations in this project.\n" : "No operations for this resource in this project.\n"
19317
20163
  );
19318
20164
  return 0;
19319
20165
  }
@@ -19548,7 +20394,7 @@ function renderProduct(product) {
19548
20394
  }
19549
20395
  const conditions = product.conditions ?? [];
19550
20396
  if (conditions.length > 0) {
19551
- lines.push("", " Needs setting up outside the platform");
20397
+ lines.push("", " Conditions");
19552
20398
  for (const condition of conditions) {
19553
20399
  lines.push(` ${condition.key ?? ""} ${condition.displayName ?? ""}`.trimEnd());
19554
20400
  }
@@ -19559,7 +20405,10 @@ function renderProduct(product) {
19559
20405
  " descriptions and value constraints are in the JSON form."
19560
20406
  );
19561
20407
  if (settings.some((setting) => setting.secret)) {
19562
- lines.push(" A credential is supplied per apply and never written into a manifest.");
20408
+ lines.push(
20409
+ " A credential is passed with --secret when a resource is created, and afterwards only",
20410
+ " when you set or change one \u2014 never written into a manifest."
20411
+ );
19563
20412
  }
19564
20413
  return lines.join("\n");
19565
20414
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@varde-flyt/vfac",
3
- "version": "0.15.2",
3
+ "version": "0.16.0",
4
4
  "description": "Deploy and manage Varde Flyt Products from a manifest.",
5
5
  "repository": {
6
6
  "type": "git",