@varde-flyt/vfac 0.15.3 → 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 (2) hide show
  1. package/dist/vfac.mjs +649 -29
  2. package/package.json +1 -1
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,6 +14322,517 @@ 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) {
@@ -14338,6 +14849,56 @@ var GovernanceFeedActorSchema = external_exports.discriminatedUnion("type", [
14338
14849
  external_exports.object({ type: external_exports.literal("resource"), id: opaqueId }),
14339
14850
  external_exports.object({ type: external_exports.literal("system") })
14340
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
+ });
14341
14902
  var GovernanceFeedEventSchema = external_exports.object({
14342
14903
  /** The audit row's id. Stable, so a workspace can recognise a re-delivery. */
14343
14904
  id: opaqueId,
@@ -15094,7 +15655,7 @@ var ProductDependencySchema = external_exports.object({
15094
15655
  var ProductDependencyV2Schema = ProductDependencySchema.extend({
15095
15656
  requiredWhen: RequiredWhenV2Schema.optional()
15096
15657
  });
15097
- function hasControlCharacter(value) {
15658
+ function hasControlCharacter2(value) {
15098
15659
  for (let i = 0; i < value.length; i += 1) {
15099
15660
  const code = value.charCodeAt(i);
15100
15661
  if (code < 32 || code === 127)
@@ -15102,7 +15663,7 @@ function hasControlCharacter(value) {
15102
15663
  }
15103
15664
  return false;
15104
15665
  }
15105
- var GuidanceTextSchema = (min, max) => external_exports.string().min(min).max(max).refine((value) => !hasControlCharacter(value), {
15666
+ var GuidanceTextSchema = (min, max) => external_exports.string().min(min).max(max).refine((value) => !hasControlCharacter2(value), {
15106
15667
  message: "Guidance is one line, with no control characters."
15107
15668
  });
15108
15669
  var ConsumerGuidanceSchema = external_exports.object({
@@ -15253,6 +15814,17 @@ var ProductDefinitionObjectSchema = external_exports.object({
15253
15814
  * Read absence through the empty array at the use site instead.
15254
15815
  */
15255
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(),
15256
15828
  lifecycle: LifecycleCapabilitiesSchema,
15257
15829
  policies: PolicyReferencesSchema,
15258
15830
  /**
@@ -16311,7 +16883,15 @@ var InstanceEnvBindingSchema = external_exports.object({
16311
16883
  "productVersion",
16312
16884
  "profile",
16313
16885
  "region",
16314
- "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"
16315
16895
  ])
16316
16896
  }).strict();
16317
16897
  var PlatformEnvBindingSchema = external_exports.object({
@@ -17353,7 +17933,7 @@ function checkDeploymentBlueprint(blueprint, ctx) {
17353
17933
  message: `Artifact source "${id}" is declared more than once.`
17354
17934
  });
17355
17935
  });
17356
- 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);
17357
17937
  [...new Set(duplicateTypes)].forEach((type) => {
17358
17938
  ctx.addIssue({
17359
17939
  code: external_exports.ZodIssueCode.custom,
@@ -17361,15 +17941,15 @@ function checkDeploymentBlueprint(blueprint, ctx) {
17361
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.`
17362
17942
  });
17363
17943
  });
17364
- (blueprint.dynamicResources ?? []).forEach((resource, index) => {
17365
- if (!sourceIds.has(resource.bootstrap.artifactSource)) {
17944
+ (blueprint.dynamicResources ?? []).forEach((resource2, index) => {
17945
+ if (!sourceIds.has(resource2.bootstrap.artifactSource)) {
17366
17946
  ctx.addIssue({
17367
17947
  code: external_exports.ZodIssueCode.custom,
17368
17948
  path: ["dynamicResources", index, "bootstrap", "artifactSource"],
17369
- 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.`
17370
17950
  });
17371
17951
  }
17372
- resource.consumers.forEach((consumerId, position) => {
17952
+ resource2.consumers.forEach((consumerId, position) => {
17373
17953
  if (!knownComponents.has(consumerId)) {
17374
17954
  ctx.addIssue({
17375
17955
  code: external_exports.ZodIssueCode.custom,
@@ -17387,7 +17967,7 @@ function checkDeploymentBlueprint(blueprint, ctx) {
17387
17967
  });
17388
17968
  }
17389
17969
  });
17390
- const handoff = resource.bootstrap.productHandoff;
17970
+ const handoff = resource2.bootstrap.productHandoff;
17391
17971
  if (handoff !== void 0) {
17392
17972
  const handoffPath = ["dynamicResources", index, "bootstrap", "productHandoff", "componentId"];
17393
17973
  const target = blueprint.components.find((component) => component.id === handoff.componentId);
@@ -17411,7 +17991,7 @@ function checkDeploymentBlueprint(blueprint, ctx) {
17411
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.`
17412
17992
  });
17413
17993
  }
17414
- if (!resource.consumers.includes(handoff.componentId)) {
17994
+ if (!resource2.consumers.includes(handoff.componentId)) {
17415
17995
  ctx.addIssue({
17416
17996
  code: external_exports.ZodIssueCode.custom,
17417
17997
  path: handoffPath,
@@ -17998,12 +18578,12 @@ function checkDeploymentBlueprint(blueprint, ctx) {
17998
18578
  message: `\`${edges.field}\` declares versions this Product ${edges.role}, but \`lifecycle.${edges.operation}.supported\` is false.`
17999
18579
  });
18000
18580
  }
18001
- const duplicates = versions.filter((version, i) => versions.indexOf(version) !== i);
18002
- if (duplicates.length > 0) {
18581
+ const duplicates2 = versions.filter((version, i) => versions.indexOf(version) !== i);
18582
+ if (duplicates2.length > 0) {
18003
18583
  ctx.addIssue({
18004
18584
  code: external_exports.ZodIssueCode.custom,
18005
18585
  path: [...edges.path],
18006
- message: `Duplicate versions: ${[...new Set(duplicates)].join(", ")}.`
18586
+ message: `Duplicate versions: ${[...new Set(duplicates2)].join(", ")}.`
18007
18587
  });
18008
18588
  }
18009
18589
  const notOlder = versions.filter((version) => compareSemver(version, blueprint.productVersion) >= 0);
@@ -18206,6 +18786,46 @@ var PUBLISH_RULE_ANNEX = {
18206
18786
  }
18207
18787
  }
18208
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
+ }
18209
18829
  }
18210
18830
  ]
18211
18831
  };
@@ -19390,21 +20010,21 @@ async function runGet(parsed, deps = {}) {
19390
20010
  `);
19391
20011
  return 0;
19392
20012
  }
19393
- const resource = result.data["resource"] ?? {};
20013
+ const resource2 = result.data["resource"] ?? {};
19394
20014
  const lines = [
19395
- `${String(resource["productInstanceId"] ?? instanceId)} ${String(resource["status"] ?? "")} ${String(resource["displayName"] ?? "")}`,
19396
- ` product ${String(resource["productId"] ?? "")} ${String(resource["productVersion"] ?? "")}`,
19397
- ` region ${String(resource["region"] ?? "")}`,
19398
- ` 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"] ?? "")}`
19399
20019
  ];
19400
- const key = resource["manifestKey"];
20020
+ const key = resource2["manifestKey"];
19401
20021
  if (typeof key === "string") lines.push(` key ${key}`);
19402
- const outputs = resource["outputs"] ?? {};
19403
- const declared = Array.isArray(resource["outputDeclarations"]) ? resource["outputDeclarations"] : [];
20022
+ const outputs = resource2["outputs"] ?? {};
20023
+ const declared = Array.isArray(resource2["outputDeclarations"]) ? resource2["outputDeclarations"] : [];
19404
20024
  const declaredNames = declared.map((entry) => entry["key"]).filter((key2) => typeof key2 === "string");
19405
20025
  const names = [.../* @__PURE__ */ new Set([...Object.keys(outputs), ...declaredNames])].sort();
19406
20026
  const isSecret2 = (name) => declared.find((entry) => entry["key"] === name)?.["secret"] === true;
19407
- const conditions = Array.isArray(resource["conditions"]) ? resource["conditions"] : [];
20027
+ const conditions = Array.isArray(resource2["conditions"]) ? resource2["conditions"] : [];
19408
20028
  if (conditions.length > 0) {
19409
20029
  lines.push("", " Operational");
19410
20030
  const width = Math.max(
@@ -19443,7 +20063,7 @@ async function runGet(parsed, deps = {}) {
19443
20063
  }
19444
20064
  }
19445
20065
  } else {
19446
- const status = String(resource["status"] ?? "");
20066
+ const status = String(resource2["status"] ?? "");
19447
20067
  lines.push(
19448
20068
  "",
19449
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."
@@ -19510,7 +20130,7 @@ This is one page. Continue with --cursor ${result.nextCursor ?? ""}
19510
20130
  process.stderr.write("No Project. Use --project prj_\u2026 or set one in your context.\n");
19511
20131
  return 2;
19512
20132
  }
19513
- const resource = parsed.flags["resource"];
20133
+ const resource2 = parsed.flags["resource"];
19514
20134
  const result = await readAllPages({
19515
20135
  endpoint: session.ready.endpoint,
19516
20136
  token: session.ready.token,
@@ -19519,7 +20139,7 @@ This is one page. Continue with --cursor ${result.nextCursor ?? ""}
19519
20139
  cursor: parsed.flags["cursor"],
19520
20140
  pageSize: parsed.flags["page-size"],
19521
20141
  onePage: true,
19522
- ...resource === void 0 ? {} : { query: { productInstanceId: resource } }
20142
+ ...resource2 === void 0 ? {} : { query: { productInstanceId: resource2 } }
19523
20143
  });
19524
20144
  if (!result.ok) {
19525
20145
  if (result.error.status === 404 && result.error.code === "NOT_FOUND") {
@@ -19539,7 +20159,7 @@ This is one page. Continue with --cursor ${result.nextCursor ?? ""}
19539
20159
  }
19540
20160
  if (result.items.length === 0) {
19541
20161
  process.stdout.write(
19542
- 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"
19543
20163
  );
19544
20164
  return 0;
19545
20165
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@varde-flyt/vfac",
3
- "version": "0.15.3",
3
+ "version": "0.16.0",
4
4
  "description": "Deploy and manage Varde Flyt Products from a manifest.",
5
5
  "repository": {
6
6
  "type": "git",