@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.
- package/README.md +4 -2
- package/dist/vfac.mjs +924 -75
- 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
|
|
95
|
-
file you commit.
|
|
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
|
|
12341
|
-
if (
|
|
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(
|
|
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
|
-
// ../
|
|
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
|
-
*
|
|
16090
|
-
*
|
|
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:
|
|
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:
|
|
16122
|
-
pathPrefix:
|
|
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
|
|
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:
|
|
16724
|
-
*
|
|
16725
|
-
*
|
|
16726
|
-
*
|
|
16727
|
-
*
|
|
16728
|
-
*
|
|
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
|
|
16733
|
-
* `_TENANT_ID
|
|
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((
|
|
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((
|
|
17167
|
-
if (!sourceIds.has(
|
|
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 "${
|
|
17949
|
+
message: `No artifact source "${resource2.bootstrap.artifactSource}" is declared. Declare it in "artifactSources" first.`
|
|
17172
17950
|
});
|
|
17173
17951
|
}
|
|
17174
|
-
|
|
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 =
|
|
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 (!
|
|
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
|
|
17248
|
-
|
|
17249
|
-
|
|
17250
|
-
|
|
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:
|
|
17254
|
-
message:
|
|
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
|
-
|
|
17264
|
-
|
|
17265
|
-
|
|
17266
|
-
|
|
17267
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
17799
|
-
if (
|
|
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(
|
|
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
|
|
20013
|
+
const resource2 = result.data["resource"] ?? {};
|
|
19168
20014
|
const lines = [
|
|
19169
|
-
`${String(
|
|
19170
|
-
` product ${String(
|
|
19171
|
-
` region ${String(
|
|
19172
|
-
` profile ${String(
|
|
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 =
|
|
20020
|
+
const key = resource2["manifestKey"];
|
|
19175
20021
|
if (typeof key === "string") lines.push(` key ${key}`);
|
|
19176
|
-
const outputs =
|
|
19177
|
-
const declared = Array.isArray(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
...
|
|
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
|
-
|
|
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("", "
|
|
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(
|
|
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
|
}
|