lib/alloc/phase/src/capacity/declaration.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const observer = @import("observer");
3 const owner = @import("owner.zig");
4 const spec = @import("spec.zig");
5
6 const OwnerShape = owner.OwnerShape;
7 const Sha256 = std.crypto.hash.sha2.Sha256;
8
9 /// This validator limit specifies a maximum of 16 covered storage records in
10 /// `SourceEnvelope.storage.covered`, requiring a valid list to be non-empty.
11 pub const covered_max: usize = 16;
12 /// This validator limit defines a maximum of 16 excluded descriptive strings in
13 /// a valid non-empty list, without enforcing a uniqueness check.
14 pub const excluded_max: usize = 16;
15 /// This validator limit defines a maximum of 8 canonical dependency
16 /// identifiers, permitting an empty list.
17 pub const dependencies_max: usize = 8;
18 /// This validator limit specifies a maximum of 24 named obligation records,
19 /// requiring a valid list to be non-empty.
20 pub const obligations_max: usize = 24;
21 /// This validator limit specifies a maximum of 512 bytes for descriptive
22 /// detail, work, or exclusion text, requiring at least 1 byte and excluding
23 /// NUL, CR, and LF characters. This bound measures raw byte length rather than
24 /// Unicode character count.
25 pub const text_bytes_max: usize = 512;
26 /// This validator limit specifies a maximum of 64 bytes for canonical dotted
27 /// claim, dependency, or trust identifiers, under the root alias
28 /// `claim_id_bytes_max`.
29 pub const id_bytes_max: usize = 64;
30 /// This validator limit specifies a maximum of 64 bytes for local
31 /// covered-region and obligation keys, which must begin with a lowercase letter
32 /// followed by lowercase letters, digits, or underscores.
33 pub const local_id_bytes_max: usize = 64;
34 /// This constant defines the 32-byte SHA-256 output digest size corresponding
35 /// to the root alias `declaration_envelope_digest_bytes`. This value reflects
36 /// the fixed digest output length rather than the input envelope size.
37 pub const digest_bytes: usize = Sha256.digest_length;
38 /// This hash domain specifies the exact string
39 /// `tiny.alloc.claim-declaration/v2` used to separate framed format inputs. The
40 /// domain tag provides input framing separation but does not offer a
41 /// collision-proof guarantee.
42 pub const digest_domain = "tiny.alloc.claim-declaration/v2";
43
44 /// `Kind` specifies source classification as either `startup_static` or
45 /// `phase_static`. Validation of `phase_static` requires valid seal and
46 /// teardown bindings. In contrast, `startup_static` does not require or inspect
47 /// optional phase bindings in `validateBindings`. The tag describes a claim and
48 /// does not govern actual allocation behavior or enforce an operational
49 /// lifetime.
50 pub const Kind = enum {
51 startup_static,
52 phase_static,
53 };
54
55 /// LimitSource indicates whether memory capacity limits originate from the
56 /// caller or from application defaults. Selecting caller forbids supplying a
57 /// default_limits selector binding. Selecting application_default requires
58 /// supplying a selector struct containing a public declaration member. The
59 /// declaration validator verifies that this selector struct exists, but it
60 /// checks no signature or value compatibility between that declaration and the
61 /// owner limits.
62 pub const LimitSource = enum {
63 caller,
64 application_default,
65 };
66
67 /// StorageLifetime labels the operational stage of a covered storage region
68 /// using the initialization, steady, or transferred tags. These values provide
69 /// descriptive metadata about when an owner uses a memory region. The enum
70 /// performs no runtime lifetime tracking, memory management, or ownership
71 /// operations.
72 pub const StorageLifetime = enum {
73 initialization,
74 steady,
75 transferred,
76 };
77
78 /// OverloadKind defines the policy an owner follows when an operation exceeds
79 /// capacity. Compatibility depends on the storage source and shape of the
80 /// owner. Exact shapes accept reject_before_seal or not_applicable. For
81 /// rejecting shapes, allocator-backed owners accept reject_before_mutation,
82 /// replace, drop, or terminal, while caller-provisioned owners accept only
83 /// reject_before_mutation. The accompanying detail string describes which
84 /// internal state is protected, such as allowing submitted and rejected
85 /// counters to increment in a full fixture. Validation checks compatibility
86 /// between the enum tag and the owner shape without inspecting operation
87 /// bodies.
88 pub const OverloadKind = enum {
89 reject_before_seal,
90 reject_before_mutation,
91 replace,
92 drop,
93 terminal,
94 not_applicable,
95 };
96
97 /// RiskStatus describes whether an identified hazard has evidentiary coverage
98 /// through the witnessed, excluded, or open tags. Setting status to witnessed
99 /// requires declaring a matching risk role obligation in the source envelope,
100 /// but it does not establish that a test passed. Setting status to excluded
101 /// documents that the hazard falls outside the operational scope of the claim.
102 /// Setting status to open documents an unresolved claim status without
103 /// satisfying an obligation requirement.
104 pub const RiskStatus = enum {
105 witnessed,
106 excluded,
107 open,
108 };
109
110 /// CoveredStorage describes a discrete memory region covered by an owner claim
111 /// through its id, lifetime, and detail fields. The id string must contain 1 to
112 /// 64 bytes, starting with a lowercase ASCII letter and followed only by
113 /// lowercase ASCII letters, digits, or underscores. Each id must be unique
114 /// within the containing covered list. The lifetime field holds a
115 /// StorageLifetime tag, and the detail string must contain 1 to 512 single-line
116 /// bytes without null bytes, carriage returns, or line feeds. The struct
117 /// borrows these slices without owning allocations or enforcing lifetimes at
118 /// runtime.
119 pub const CoveredStorage = struct {
120 id: []const u8,
121 lifetime: StorageLifetime,
122 detail: []const u8,
123 };
124
125 /// Storage specifies the memory coverage boundaries of a claim using covered
126 /// and excluded slices, rather than holding the backing storage buffer of an
127 /// owner. The covered list accepts 1 to 16 CoveredStorage records with unique
128 /// local identifiers. The excluded list accepts 1 to 16 descriptive strings,
129 /// each bounded to 1 to 512 single-line bytes excluding null bytes, carriage
130 /// returns, and line feeds. Excluded entries are not checked for uniqueness,
131 /// and their presence makes no claim that other process memory is absent. The
132 /// struct holds borrowed slices rather than copying text or managing memory.
133 pub const Storage = struct {
134 covered: []const CoveredStorage,
135 excluded: []const []const u8,
136 };
137
138 /// `Overload` describes declared overload behavior through its `kind` and
139 /// `detail` fields, where `detail` identifies protected state. Full declaration
140 /// validation checks that `kind` is compatible with `OwnerShape` and verifies
141 /// that `detail` contains 1 to 512 bytes excluding NUL, LF, and CR characters.
142 /// Merely constructing the record validates nothing and installs no dynamic
143 /// policy.
144 pub const Overload = struct {
145 kind: OverloadKind,
146 detail: []const u8,
147 };
148
149 /// Risk pairs a RiskStatus tag with a descriptive detail string explaining an
150 /// operational boundary hazard. The detail string must contain 1 to 512
151 /// single-line bytes without null characters, carriage returns, or line feeds.
152 /// Declaration validation inspects the detail text grammar and verifies that
153 /// any risk marked witnessed has a corresponding obligation role declared in
154 /// the source envelope. The struct retains these values as source metadata
155 /// without executing checks at runtime.
156 pub const Risk = struct {
157 status: RiskStatus,
158 detail: []const u8,
159 };
160
161 /// Risks groups the operational hazards of an owner into transitive and foreign
162 /// Risk records. The transitive field documents risks originating within
163 /// callees or subsidiary components, while the foreign field documents risks
164 /// arising from external resources. The descriptive details define the actual
165 /// scope of each hazard. Declaration validation enforces grammar and witnessed
166 /// obligations for both fields, but it does not compute or audit a transitive
167 /// closure across components.
168 pub const Risks = struct {
169 transitive: Risk,
170 foreign: Risk,
171 };
172
173 /// `Work` defines an `equation` field consisting of descriptive work bound text
174 /// between 1 and 512 bytes long, excluding NUL, LF, and CR characters. It is
175 /// required whenever an owner uses a caller-provisioned shape, and its presence
176 /// requires declaring an obligation with the work_bound role. The equation text
177 /// provides descriptive documentation that is neither parsed nor evaluated
178 /// during validation. This record differs from the WorkLimits structure, which
179 /// supplies concrete numeric limits.
180 pub const Work = struct {
181 equation: []const u8,
182 };
183
184 /// ObligationRole classifies the evidentiary purpose of a named obligation
185 /// within a claim. Roles identify evidence requirements including
186 /// capacity_model, overload, work_bound, transitive_risk, foreign_risk, seal,
187 /// and teardown. Declaration validation enforces role presence rules for
188 /// selected source fields, requiring capacity_model for every claim, overload
189 /// unless overload handling is not applicable, work_bound when a work equation
190 /// is present, and matching risk roles when transitive or foreign risks are
191 /// witnessed. Assigning a role classifies the requirement within the
192 /// declaration but does not establish that evidence exists or that any test
193 /// passed.
194 pub const ObligationRole = enum {
195 capacity_model,
196 acquisition,
197 initialization_failure,
198 overload,
199 seal,
200 teardown,
201 work_bound,
202 transitive_risk,
203 foreign_risk,
204 integration,
205 custom,
206 };
207
208 /// Obligation pairs a claim-local identifier key with an ObligationRole tag.
209 /// The key string must contain 1 to 64 bytes, beginning with a lowercase ASCII
210 /// letter and containing only lowercase letters, digits, or underscores. The
211 /// source envelope accepts a list of 1 to 24 obligations, and every key must be
212 /// unique within that list. Validation mandates the capacity_model role in
213 /// every claim, the overload role unless overload is not applicable,
214 /// appropriate risk roles for witnessed risks, and the work_bound role when a
215 /// work equation is declared. Keys remain local to the enclosing claim rather
216 /// than resolving globally.
217 pub const Obligation = struct {
218 key: []const u8,
219 role: ObligationRole,
220 };
221
222 /// `WitnessAnnotation` holds `claim_id` and `obligation_key` slices alongside a
223 /// `role` value. The `witness` operation requires `Owner.claim` to have the
224 /// exact type `Declaration` and verifies that the obligation key exists, but it
225 /// does not validate the whole claim. Passing the returned annotation to
226 /// `record` associates it with the enclosing test. Constructing the annotation
227 /// alone does not record an association with a test or prove that a test
228 /// passed, and it performs no test execution or assertion inspection.
229 pub const WitnessAnnotation = struct {
230 claim_id: []const u8,
231 obligation_key: []const u8,
232 role: ObligationRole,
233 };
234
235 /// SourceEnvelope contains the primary specification metadata of a claim,
236 /// gathering its id, kind, limit_source, storage, capacity, overload, risks,
237 /// optional work, dependencies, and obligations. The `id` must be a canonical
238 /// identifier of 1 to 64 bytes consisting of at least two non-empty dot
239 /// segments composed of lowercase letters, digits, or underscores, where
240 /// leading digits and underscores are permitted. The `capacity` field is a
241 /// typed `Spec` with semantic type fields requiring a compile-time
242 /// representation rather than expression evaluation. The `dependencies` slice
243 /// accepts up to 8 canonical identifiers, rejects duplicates or
244 /// self-references, and performs no registry lookup or cycle analysis. The
245 /// envelope borrows its referenced slices rather than deep-copying runtime
246 /// strings.
247 pub const SourceEnvelope = struct {
248 id: []const u8,
249 kind: Kind,
250 limit_source: LimitSource,
251 storage: Storage,
252 capacity: spec.Spec,
253 overload: Overload,
254 risks: Risks,
255 work: ?Work = null,
256 dependencies: []const []const u8 = &.{},
257 obligations: []const Obligation,
258 };
259
260 /// EnvelopeView provides a structurally uniform representation of a
261 /// SourceEnvelope where the typed capacity specification is replaced by a
262 /// SpecView. The remaining borrowed metadata fields, including id, kind,
263 /// limit_source, storage, overload, risks, work, dependencies, and obligations,
264 /// remain identical. This view enables structural serialization and digest
265 /// computation without depending on semantic type coordinates. Slices
266 /// referenced by the view remain borrowed and are not owned by the view struct.
267 pub const EnvelopeView = struct {
268 id: []const u8,
269 kind: Kind,
270 limit_source: LimitSource,
271 storage: Storage,
272 capacity: spec.View,
273 overload: Overload,
274 risks: Risks,
275 work: ?Work = null,
276 dependencies: []const []const u8 = &.{},
277 obligations: []const Obligation,
278 };
279
280 /// PremiseClass names the form of basis that supports a phase binding. Its tags
281 /// distinguish between mathematical theorem domains, checked semantic or path
282 /// facts, certified claim summaries, and trusted user, external, or environment
283 /// assertions. Selecting a tag identifies the expected authority category
284 /// during premise validation, but the enum itself does not establish or
285 /// evaluate the underlying evidence.
286 pub const PremiseClass = enum {
287 theorem_domain,
288 checked_semantic_fact,
289 checked_path_fact,
290 certified_summary,
291 trusted_user,
292 trusted_extern,
293 trusted_environment,
294 };
295
296 /// ObligationSelector identifies a claim-local obligation that grounds a
297 /// theorem premise through its key field. Validation checks that the key is a
298 /// valid local identifier and that it matches an obligation declared in the
299 /// current source envelope. The check enforces no role restriction on the
300 /// targeted obligation and performs no evaluation of the theorem.
301 pub const ObligationSelector = struct {
302 key: []const u8,
303 };
304
305 /// ClaimSelector designates an external claim supporting a certified summary
306 /// premise through its id field. Validation confirms that the id string
307 /// satisfies the syntax of a canonical dotted identifier containing at least
308 /// two segments. The check verifies identifier syntax only and does not look up
309 /// or confirm the existence of the referenced claim.
310 pub const ClaimSelector = struct {
311 id: []const u8,
312 };
313
314 /// TrustDeclarationSelector identifies a trust declaration supporting a trusted
315 /// user, external component, or environment premise through its id field.
316 /// Validation checks that the id string conforms to canonical dotted identifier
317 /// syntax. The validator performs no resolution of the external identifier and
318 /// does not validate the underlying trust declaration.
319 pub const TrustDeclarationSelector = struct {
320 id: []const u8,
321 };
322
323 /// PremiseAuthority specifies the justifying source for a premise as a tagged
324 /// union over checker, theorem, certified_claim, and trusted variants. Each
325 /// variant carries its respective selector, except checker, which carries no
326 /// payload. The authority must pair with a compatible PremiseClass during
327 /// validation: checker requires checked_semantic_fact or checked_path_fact,
328 /// theorem requires theorem_domain, certified_claim requires certified_summary,
329 /// and trusted requires trusted_user, trusted_extern, or trusted_environment.
330 pub const PremiseAuthority = union(enum) {
331 checker,
332 theorem: ObligationSelector,
333 certified_claim: ClaimSelector,
334 trusted: TrustDeclarationSelector,
335 };
336
337 /// Premise pairs a PremiseClass category with a PremiseAuthority variant to
338 /// justify a phase lifecycle binding. The validatePremise function checks that
339 /// the class and authority are mutually compatible, that selector identifiers
340 /// follow required canonical or local syntax, and that local theorem keys exist
341 /// within the source obligations. Validation confirms structural and syntactic
342 /// compatibility but produces no proof of the underlying fact.
343 pub const Premise = struct {
344 class: PremiseClass,
345 authority: PremiseAuthority,
346 };
347
348 /// PhaseBinding associates a lifecycle transition with an implementation seam
349 /// and an evidentiary premise through its family and premise fields. Both
350 /// fields default to null, but validating a phase_static declaration requires
351 /// both to be present. The family field must be a selector struct containing a
352 /// public declaration member that resolves to a function. Validation does not
353 /// inspect the function signature or body, nor does it require the function to
354 /// match Owner.activate or Owner.deinit. The struct stores compile-time type
355 /// and premise descriptors rather than a runtime callable closure.
356 pub const PhaseBinding = struct {
357 family: ?type = null,
358 premise: ?Premise = null,
359 };
360
361 /// Bindings gathers compile-time type coordinates and lifecycle phase bindings
362 /// separate from the source envelope. Fields include owner, default_limits,
363 /// seal, teardown, and source, which all default to null. Validation requires
364 /// owner to match the Owner type supplied to validateDeclaration. The
365 /// default_limits binding is required for application_default limit sources and
366 /// forbidden for caller limit sources, while seal and teardown bindings are
367 /// required for phase_static declarations. The optional source type is retained
368 /// as metadata but is not inspected by the validator. Envelope digests exclude
369 /// all bindings and premises, and the struct owns no heap memory or reference
370 /// counts.
371 pub const Bindings = struct {
372 owner: ?type = null,
373 default_limits: ?type = null,
374 seal: ?PhaseBinding = null,
375 teardown: ?PhaseBinding = null,
376 source: ?type = null,
377 };
378
379 /// Declaration couples a SourceEnvelope specification with its corresponding
380 /// compile-time Bindings. The struct acts as a typed metadata container rather
381 /// than a runtime resource allocation. Calling validateDeclaration checks the
382 /// declaration against a specified Owner type, expected OwnerShape, and source
383 /// constraints, but validating a declaration does not verify the whole owner
384 /// shape by itself. Constructing or validating a declaration installs no
385 /// automatic runtime enforcement.
386 pub const Declaration = struct {
387 source: SourceEnvelope,
388 bindings: Bindings,
389 };
390
391 /// Violation enumerates the failure categories returned by the source
392 /// declaration validator. When validation fails, the validator returns the
393 /// first detected violation category rather than reporting a runtime allocation
394 /// error. Malformed user types supplied in bindings may trigger Zig compile
395 /// errors before validation completes. In root.zig, this type is exported under
396 /// the public alias DeclarationViolation.
397 pub const Violation = enum {
398 id_format,
399 storage_covered_missing,
400 storage_covered_overflow,
401 storage_region_id_invalid,
402 storage_region_duplicate,
403 storage_detail_invalid,
404 storage_excluded_missing,
405 storage_excluded_overflow,
406 storage_excluded_invalid,
407 capacity_limits_mismatch,
408 capacity_field_path_invalid,
409 capacity_type_selector_invalid,
410 capacity_node_outside_fragment,
411 capacity_assertion_invalid,
412 overload_detail_invalid,
413 overload_shape_incompatible,
414 risk_detail_invalid,
415 risk_obligation_missing,
416 work_missing,
417 work_equation_invalid,
418 work_obligation_missing,
419 dependencies_overflow,
420 dependency_format,
421 dependency_duplicate,
422 dependency_self,
423 obligations_missing,
424 obligations_overflow,
425 obligation_key_invalid,
426 obligation_key_duplicate,
427 capacity_model_obligation_missing,
428 overload_obligation_missing,
429 owner_binding_missing,
430 owner_binding_mismatch,
431 default_limits_binding_missing,
432 default_limits_binding_unexpected,
433 default_limits_binding_invalid,
434 seal_binding_missing,
435 teardown_binding_missing,
436 seam_family_missing,
437 seam_family_invalid,
438 premise_missing,
439 premise_authority_mismatch,
440 premise_selector_invalid,
441 premise_obligation_unknown,
442 };
443
444 /// selector takes any compile-time value and returns an anonymous struct type
445 /// containing a public constant named declaration initialized to that value.
446 /// This helper allows compile-time type fields to transport typed references
447 /// and values. The requirement that declaration resolve to a function is not
448 /// checked by selector itself, but is imposed only when validating a phase
449 /// binding family. The function neither validates the wrapped value nor creates
450 /// a callable runtime closure.
451 pub fn selector(comptime value: anytype) type {
452 return struct {
453 pub const declaration = value;
454 };
455 }
456
457 /// sourceView is an internal implementation function in declaration.zig that
458 /// converts a compile-time SourceEnvelope into an EnvelopeView, and it is not
459 /// exported from root.zig. It converts the typed capacity specification into a
460 /// SpecView using spec.view, transforming typed inputs and type selectors into
461 /// stable compile-time views while omitting semantic limit types and selected
462 /// type facts. Other source slices and metadata fields are transferred directly
463 /// into the view. The function performs no validation on the converted
464 /// envelope.
465 pub fn sourceView(comptime value: SourceEnvelope) EnvelopeView {
466 return .{
467 .id = value.id,
468 .kind = value.kind,
469 .limit_source = value.limit_source,
470 .storage = value.storage,
471 .capacity = spec.view(value.capacity),
472 .overload = value.overload,
473 .risks = value.risks,
474 .work = value.work,
475 .dependencies = value.dependencies,
476 .obligations = value.obligations,
477 };
478 }
479
480 /// envelopeDigest hashes the content of an EnvelopeView using SHA-256 under the
481 /// domain tiny.alloc.claim-declaration/v2, and is exported from root.zig as
482 /// declarationSourceEnvelopeDigest. The digest builder length-frames every
483 /// field and ordered list element, incorporating storage details, the capacity
484 /// specification digest, overload policy, risks, optional work equations,
485 /// dependencies, and obligations. Because the view contains no bindings, all
486 /// typed bindings and premises are excluded from the digest. The function
487 /// performs no validation or formal proof, requiring callers to provide a valid
488 /// bounded view.
489 pub fn envelopeDigest(value: EnvelopeView) [digest_bytes]u8 {
490 var builder = DigestBuilder.init();
491 builder.addBytes("id", value.id);
492 builder.addEnum("kind", value.kind);
493 builder.addEnum("limit_source", value.limit_source);
494 addStorage(&builder, value.storage);
495 builder.addDigest("capacity", spec.digest(value.capacity));
496 addOverload(&builder, value.overload);
497 addRisk(&builder, "transitive", value.risks.transitive);
498 addRisk(&builder, "foreign", value.risks.foreign);
499 addWork(&builder, value.work);
500 addTextList(&builder, "dependencies", value.dependencies);
501 addObligations(&builder, value.obligations);
502 return builder.finish();
503 }
504
505 /// declarationEnvelopeDigest computes the digest of a compile-time Declaration
506 /// by converting its source envelope through sourceView and hashing the
507 /// resulting EnvelopeView with envelopeDigest. Because the view omits bindings,
508 /// changes to bindings such as the owner type, seam functions, or premises do
509 /// not affect the digest. The underlying capacity digest also omits semantic
510 /// types and sizing facts. The resulting hash establishes a content identity
511 /// for selected source specification fields rather than an identity for the
512 /// whole implementation or its evidence.
513 pub fn declarationEnvelopeDigest(comptime value: Declaration) [digest_bytes]u8 {
514 return envelopeDigest(sourceView(value.source));
515 }
516
517 /// validate evaluates a compile-time Declaration against an Owner type and an
518 /// OwnerShape, returning the first detected Violation or null if validation
519 /// passes, and is exported from root.zig as validateDeclaration. It checks
520 /// identifier syntax, storage limits, the typed capacity specification against
521 /// Owner.Limits, overload compatibility and detail text, risks, work equations,
522 /// dependencies, obligations, and bindings. The function does not run the owner
523 /// shape validator or record observer claims. Returning null
524 /// indicates only that the declaration metadata is valid. The Owner type must
525 /// define a Limits type compatible with the capacity specification, and the
526 /// function makes no promise of universal safety for arbitrary type
527 /// introspection.
528 pub fn validate(
529 comptime Owner: type,
530 comptime value: Declaration,
531 comptime shape: OwnerShape,
532 ) ?Violation {
533 if (!identifierValid(value.source.id)) return .id_format;
534 if (validateStorage(value.source.storage)) |violation| return violation;
535 if (spec.validate(Owner.Limits, value.source.capacity)) |violation| {
536 return capacityViolation(violation);
537 }
538 if (!textValid(value.source.overload.detail)) {
539 return .overload_detail_invalid;
540 }
541 if (!overloadCompatible(value.source.overload.kind, shape)) {
542 return .overload_shape_incompatible;
543 }
544 if (validateRisk(value.source.risks.transitive)) |violation| return violation;
545 if (validateRisk(value.source.risks.foreign)) |violation| return violation;
546 if (validateWork(value.source.work, shape)) |violation| return violation;
547 if (validateDependencies(value.source)) |violation| return violation;
548 if (validateObligations(value.source)) |violation| return violation;
549 if (validateBindings(Owner, value)) |violation| return violation;
550 return null;
551 }
552
553 /// require is a public function within declaration.zig that is not re-exported
554 /// from root.zig. It executes the same compile-time checks as validate, but it
555 /// sets the evaluation branch quota to 1,000,000 and emits a compile error
556 /// containing the violation tag name upon failure instead of returning an
557 /// optional value. The function performs validation enforcement during
558 /// compilation and does not record claim metadata or test annotations.
559 pub fn require(
560 comptime Owner: type,
561 comptime value: Declaration,
562 comptime shape: OwnerShape,
563 ) void {
564 @setEvalBranchQuota(1_000_000);
565 if (comptime validate(Owner, value, shape)) |violation| {
566 @compileError("invalid claim declaration: " ++ @tagName(violation));
567 }
568 }
569
570 /// witness extracts a WitnessAnnotation for a specified Owner type and
571 /// obligation key at compile time. It requires that Owner declare a public
572 /// member named claim whose type is exactly Declaration, and that the
573 /// obligation key exists within the claim obligations list, failing compilation
574 /// if either condition is not met. It returns a WitnessAnnotation containing
575 /// the claim id, obligation key, and associated obligation role from the source
576 /// envelope. The function does not run full declaration validation, inspect
577 /// test logic, or execute tests.
578 pub fn witness(
579 comptime Owner: type,
580 comptime obligation_key: []const u8,
581 ) WitnessAnnotation {
582 if (comptime !@hasDecl(Owner, "claim") or
583 @TypeOf(Owner.claim) != Declaration)
584 {
585 @compileError("test obligation owner lacks a typed claim declaration");
586 }
587 const role = comptime obligationRole(
588 Owner.claim.source.obligations,
589 obligation_key,
590 ) orelse @compileError("test obligation key is not declared by its owner");
591 return .{
592 .claim_id = Owner.claim.source.id,
593 .obligation_key = obligation_key,
594 .role = role,
595 };
596 }
597
598 /// `record` enters a witness annotation into the Stardust claim table for the
599 /// enclosing test. It is `inline`, so the test stays the compiler's analysis
600 /// owner and the recorded row names that test even outside `comptime`. Without
601 /// the Stardust observer, as under an unpatched compiler or in a release, the
602 /// call records nothing, while `witness` still checks the obligation key.
603 pub inline fn record(comptime annotation: WitnessAnnotation) void {
604 comptime observer.witness(annotation);
605 }
606
607 /// `validatePremise` accepts a compile-time `SourceEnvelope` and premise pair
608 /// and returns the first optional `Violation` or null. It checks classification
609 /// and authority compatibility between the supplied entities. For `theorem`,
610 /// the function validates the local key and its existence in source obligations
611 /// without applying role restrictions. Both `certified_claim` and `trusted`
612 /// validate canonical `id` syntax without performing a registry lookup. The
613 /// function does not validate the whole `SourceEnvelope` or inspect underlying
614 /// evidence.
615 pub fn validatePremise(
616 comptime source: SourceEnvelope,
617 comptime premise: Premise,
618 ) ?Violation {
619 if (!premiseAuthorityCompatible(premise)) {
620 return .premise_authority_mismatch;
621 }
622 switch (premise.authority) {
623 .checker => {},
624 .theorem => |selected| {
625 if (!localIdentifierValid(selected.key)) {
626 return .premise_selector_invalid;
627 }
628 if (!obligationKnown(source.obligations, selected.key)) {
629 return .premise_obligation_unknown;
630 }
631 },
632 .certified_claim => |selected| {
633 if (!identifierValid(selected.id)) {
634 return .premise_selector_invalid;
635 }
636 },
637 .trusted => |selected| {
638 if (!identifierValid(selected.id)) {
639 return .premise_selector_invalid;
640 }
641 },
642 }
643 return null;
644 }
645
646 /// declareWarmRetained declares a weak capacity owner associated with warm
647 /// retained memory. It checks at compile time that the provided id satisfies
648 /// canonical dotted identifier syntax and discards the Owner type parameter.
649 /// The function records no observer claims and performs no actual
650 /// retention policy, owner shape, or capacity bound checks.
651 pub fn declareWarmRetained(comptime id: []const u8, comptime Owner: type) void {
652 requireCanonicalId(id);
653 _ = Owner;
654 }
655
656 /// The current implementation of `declareDynamicUnbounded` checks a canonical
657 /// dotted `id` at compile time and discards `Owner`. It does not inspect
658 /// whether `Owner` actually allocates, install behavior, record observer
659 /// metadata, or register a proven bound.
660 pub fn declareDynamicUnbounded(comptime id: []const u8, comptime Owner: type) void {
661 requireCanonicalId(id);
662 _ = Owner;
663 }
664
665 fn validateStorage(storage: Storage) ?Violation {
666 if (storage.covered.len == 0) return .storage_covered_missing;
667 if (storage.covered.len > covered_max) return .storage_covered_overflow;
668 for (storage.covered, 0..) |region, index| {
669 if (!localIdentifierValid(region.id)) return .storage_region_id_invalid;
670 if (!textValid(region.detail)) return .storage_detail_invalid;
671 for (storage.covered[0..index]) |earlier| {
672 if (std.mem.eql(u8, earlier.id, region.id)) {
673 return .storage_region_duplicate;
674 }
675 }
676 }
677 if (storage.excluded.len == 0) return .storage_excluded_missing;
678 if (storage.excluded.len > excluded_max) return .storage_excluded_overflow;
679 for (storage.excluded) |excluded| {
680 if (!textValid(excluded)) return .storage_excluded_invalid;
681 }
682 return null;
683 }
684
685 fn obligationRole(
686 comptime obligations: []const Obligation,
687 comptime key: []const u8,
688 ) ?ObligationRole {
689 for (obligations) |obligation| {
690 if (std.mem.eql(u8, obligation.key, key)) return obligation.role;
691 }
692 return null;
693 }
694
695 fn capacityViolation(violation: spec.Violation) Violation {
696 return switch (violation) {
697 .limits_type_mismatch => .capacity_limits_mismatch,
698 .input_field_path_invalid => .capacity_field_path_invalid,
699 .type_selector_overflow,
700 .type_selector_id_invalid,
701 .type_selector_id_duplicate,
702 .type_selector_fact_invalid,
703 => .capacity_type_selector_invalid,
704 .node_outside_fragment => .capacity_node_outside_fragment,
705 else => .capacity_assertion_invalid,
706 };
707 }
708
709 fn validateRisk(risk: Risk) ?Violation {
710 if (!textValid(risk.detail)) return .risk_detail_invalid;
711 return null;
712 }
713
714 fn validateWork(work: ?Work, shape: OwnerShape) ?Violation {
715 const value = work orelse {
716 if (shape.storage_source == .caller_provisioned) return .work_missing;
717 return null;
718 };
719 if (!textValid(value.equation)) return .work_equation_invalid;
720 return null;
721 }
722
723 fn validateDependencies(source: SourceEnvelope) ?Violation {
724 if (source.dependencies.len > dependencies_max) return .dependencies_overflow;
725 for (source.dependencies, 0..) |dependency, index| {
726 if (!identifierValid(dependency)) return .dependency_format;
727 if (std.mem.eql(u8, dependency, source.id)) return .dependency_self;
728 for (source.dependencies[0..index]) |earlier| {
729 if (std.mem.eql(u8, earlier, dependency)) return .dependency_duplicate;
730 }
731 }
732 return null;
733 }
734
735 fn validateObligations(source: SourceEnvelope) ?Violation {
736 if (source.obligations.len == 0) return .obligations_missing;
737 if (source.obligations.len > obligations_max) return .obligations_overflow;
738 for (source.obligations, 0..) |obligation, index| {
739 if (!localIdentifierValid(obligation.key)) return .obligation_key_invalid;
740 for (source.obligations[0..index]) |earlier| {
741 if (std.mem.eql(u8, earlier.key, obligation.key)) {
742 return .obligation_key_duplicate;
743 }
744 }
745 }
746 if (!obligationRoleKnown(source.obligations, .capacity_model)) {
747 return .capacity_model_obligation_missing;
748 }
749 if (source.overload.kind != .not_applicable and
750 !obligationRoleKnown(source.obligations, .overload))
751 {
752 return .overload_obligation_missing;
753 }
754 if (source.risks.transitive.status == .witnessed and
755 !obligationRoleKnown(source.obligations, .transitive_risk))
756 {
757 return .risk_obligation_missing;
758 }
759 if (source.risks.foreign.status == .witnessed and
760 !obligationRoleKnown(source.obligations, .foreign_risk))
761 {
762 return .risk_obligation_missing;
763 }
764 if (source.work != null and
765 !obligationRoleKnown(source.obligations, .work_bound))
766 {
767 return .work_obligation_missing;
768 }
769 return null;
770 }
771
772 fn validateBindings(comptime Owner: type, comptime value: Declaration) ?Violation {
773 const bindings = value.bindings;
774 const bound_owner = bindings.owner orelse return .owner_binding_missing;
775 if (bound_owner != Owner) return .owner_binding_mismatch;
776 switch (value.source.limit_source) {
777 .caller => if (bindings.default_limits != null) {
778 return .default_limits_binding_unexpected;
779 },
780 .application_default => {
781 const Selector = bindings.default_limits orelse {
782 return .default_limits_binding_missing;
783 };
784 if (!selectorValid(Selector)) return .default_limits_binding_invalid;
785 },
786 }
787 if (value.source.kind == .phase_static) {
788 const seal = bindings.seal orelse return .seal_binding_missing;
789 const teardown = bindings.teardown orelse return .teardown_binding_missing;
790 if (validatePhaseBinding(value.source, seal)) |violation| return violation;
791 if (validatePhaseBinding(value.source, teardown)) |violation| return violation;
792 }
793 return null;
794 }
795
796 fn validatePhaseBinding(
797 comptime source: SourceEnvelope,
798 comptime binding: PhaseBinding,
799 ) ?Violation {
800 const Family = binding.family orelse return .seam_family_missing;
801 if (!selectorValid(Family)) return .seam_family_invalid;
802 if (@typeInfo(@TypeOf(Family.declaration)) != .@"fn") {
803 return .seam_family_invalid;
804 }
805 const premise = binding.premise orelse return .premise_missing;
806 return validatePremise(source, premise);
807 }
808
809 fn selectorValid(comptime Selector: type) bool {
810 return @typeInfo(Selector) == .@"struct" and
811 @hasDecl(Selector, "declaration");
812 }
813
814 fn premiseAuthorityCompatible(premise: Premise) bool {
815 return switch (premise.authority) {
816 .checker => premise.class == .checked_semantic_fact or
817 premise.class == .checked_path_fact,
818 .theorem => premise.class == .theorem_domain,
819 .certified_claim => premise.class == .certified_summary,
820 .trusted => premise.class == .trusted_user or
821 premise.class == .trusted_extern or
822 premise.class == .trusted_environment,
823 };
824 }
825
826 fn overloadCompatible(kind: OverloadKind, shape: OwnerShape) bool {
827 return switch (shape.storage_source) {
828 .allocator_backed => switch (shape.overload_shape) {
829 .exact => kind == .reject_before_seal or kind == .not_applicable,
830 .rejecting => switch (kind) {
831 .reject_before_mutation, .replace, .drop, .terminal => true,
832 else => false,
833 },
834 },
835 .caller_provisioned => switch (shape.overload_shape) {
836 .exact => kind == .reject_before_seal or kind == .not_applicable,
837 .rejecting => kind == .reject_before_mutation,
838 },
839 };
840 }
841
842 fn obligationKnown(obligations: []const Obligation, key: []const u8) bool {
843 for (obligations) |obligation| {
844 if (std.mem.eql(u8, obligation.key, key)) return true;
845 }
846 return false;
847 }
848
849 fn obligationRoleKnown(
850 obligations: []const Obligation,
851 role: ObligationRole,
852 ) bool {
853 for (obligations) |obligation| {
854 if (obligation.role == role) return true;
855 }
856 return false;
857 }
858
859 fn localIdentifierValid(value: []const u8) bool {
860 if (value.len == 0 or value.len > local_id_bytes_max) return false;
861 if (value[0] < 'a' or value[0] > 'z') return false;
862 for (value[1..]) |byte| {
863 const lower = byte >= 'a' and byte <= 'z';
864 const digit = byte >= '0' and byte <= '9';
865 if (!lower and !digit and byte != '_') return false;
866 }
867 return true;
868 }
869
870 /// identifierValid checks whether a byte slice adheres to canonical dotted
871 /// identifier grammar, returning true on success. The slice must contain 1 to
872 /// 64 bytes divided into at least two non-empty segments separated by dots.
873 /// Segment characters are restricted to lowercase ASCII letters, digits, and
874 /// underscores, and segments may begin with a digit or underscore. The function
875 /// validates canonical identifier syntax only, without performing external
876 /// registry lookups or enforcing claim-local key grammar.
877 pub fn identifierValid(id: []const u8) bool {
878 if (id.len == 0 or id.len > id_bytes_max) return false;
879 var segments: usize = 1;
880 var segment_length: usize = 0;
881 for (id) |byte| {
882 if (byte == '.') {
883 if (segment_length == 0) return false;
884 segments += 1;
885 segment_length = 0;
886 continue;
887 }
888 const lower = byte >= 'a' and byte <= 'z';
889 const digit = byte >= '0' and byte <= '9';
890 if (!lower and !digit and byte != '_') return false;
891 segment_length += 1;
892 }
893 return segment_length != 0 and segments >= 2;
894 }
895
896 fn textValid(value: []const u8) bool {
897 if (value.len == 0 or value.len > text_bytes_max) return false;
898 for (value) |byte| {
899 if (byte == 0 or byte == '\n' or byte == '\r') return false;
900 }
901 return true;
902 }
903
904 fn requireCanonicalId(comptime id: []const u8) void {
905 if (comptime !identifierValid(id)) {
906 @compileError("invalid memory owner id: " ++ id);
907 }
908 }
909
910 const DigestBuilder = struct {
911 state: Sha256,
912
913 fn init() DigestBuilder {
914 var result = DigestBuilder{ .state = Sha256.init(.{}) };
915 result.addFramed(digest_domain);
916 return result;
917 }
918
919 fn addBytes(self: *DigestBuilder, name: []const u8, value: []const u8) void {
920 self.addFramed(name);
921 self.addFramed(value);
922 }
923
924 fn addEnum(self: *DigestBuilder, name: []const u8, value: anytype) void {
925 self.addBytes(name, @tagName(value));
926 }
927
928 fn addCount(self: *DigestBuilder, name: []const u8, value: usize) void {
929 self.addFramed(name);
930 self.addU64(value);
931 }
932
933 fn addDigest(
934 self: *DigestBuilder,
935 name: []const u8,
936 value: [digest_bytes]u8,
937 ) void {
938 self.addBytes(name, &value);
939 }
940
941 fn addU64(self: *DigestBuilder, value: u64) void {
942 var encoded: [8]u8 = undefined;
943 std.mem.writeInt(u64, &encoded, value, .little);
944 self.addFramed(&encoded);
945 }
946
947 fn finish(self: *DigestBuilder) [digest_bytes]u8 {
948 var result: [digest_bytes]u8 = undefined;
949 self.state.final(&result);
950 return result;
951 }
952
953 fn addFramed(self: *DigestBuilder, value: []const u8) void {
954 var length: [8]u8 = undefined;
955 std.mem.writeInt(u64, &length, value.len, .little);
956 self.state.update(&length);
957 self.state.update(value);
958 }
959 };
960
961 fn addStorage(builder: *DigestBuilder, storage: Storage) void {
962 builder.addCount("covered", storage.covered.len);
963 for (storage.covered) |region| {
964 builder.addBytes("region_id", region.id);
965 builder.addEnum("lifetime", region.lifetime);
966 builder.addBytes("detail", region.detail);
967 }
968 addTextList(builder, "excluded", storage.excluded);
969 }
970
971 fn addOverload(builder: *DigestBuilder, overload: Overload) void {
972 builder.addEnum("overload_kind", overload.kind);
973 builder.addBytes("overload_detail", overload.detail);
974 }
975
976 fn addRisk(builder: *DigestBuilder, name: []const u8, risk: Risk) void {
977 builder.addBytes("risk", name);
978 builder.addEnum("risk_status", risk.status);
979 builder.addBytes("risk_detail", risk.detail);
980 }
981
982 fn addWork(builder: *DigestBuilder, work: ?Work) void {
983 builder.addCount("work", @intFromBool(work != null));
984 if (work) |value| builder.addBytes("work_equation", value.equation);
985 }
986
987 fn addTextList(
988 builder: *DigestBuilder,
989 name: []const u8,
990 values: []const []const u8,
991 ) void {
992 builder.addCount(name, values.len);
993 for (values) |value| builder.addBytes("item", value);
994 }
995
996 fn addObligations(builder: *DigestBuilder, values: []const Obligation) void {
997 builder.addCount("obligations", values.len);
998 for (values) |value| {
999 builder.addBytes("obligation_key", value.key);
1000 builder.addEnum("obligation_role", value.role);
1001 }
1002 }
1003
1004 const PremiseTestSource = SourceEnvelope{
1005 .id = "test.premises",
1006 .kind = .phase_static,
1007 .limit_source = .caller,
1008 .storage = .{
1009 .covered = &.{.{
1010 .id = "state",
1011 .lifetime = .steady,
1012 .detail = "test state",
1013 }},
1014 .excluded = &.{"caller values"},
1015 },
1016 .capacity = .{
1017 .inputs = &.{},
1018 .nodes = &.{.{ .constant = 1 }},
1019 .assertions = &.{.{
1020 .scope = .closure_total,
1021 .measure = .retained,
1022 .relation = .exact,
1023 .expression = 0,
1024 }},
1025 },
1026 .overload = .{
1027 .kind = .reject_before_seal,
1028 .detail = "test admission rejects",
1029 },
1030 .risks = .{
1031 .transitive = .{ .status = .open, .detail = "test callees remain open" },
1032 .foreign = .{ .status = .open, .detail = "test foreign state remains open" },
1033 },
1034 .obligations = &.{
1035 .{ .key = "capacity", .role = .capacity_model },
1036 .{ .key = "overload", .role = .overload },
1037 .{ .key = "theorem", .role = .seal },
1038 },
1039 };
1040
1041 test "premise declarations cover every authority class" {
1042 const premises = [_]Premise{
1043 .{ .class = .theorem_domain, .authority = .{
1044 .theorem = .{ .key = "theorem" },
1045 } },
1046 .{ .class = .checked_semantic_fact, .authority = .checker },
1047 .{ .class = .checked_path_fact, .authority = .checker },
1048 .{ .class = .certified_summary, .authority = .{
1049 .certified_claim = .{ .id = "other.claim" },
1050 } },
1051 .{ .class = .trusted_user, .authority = .{
1052 .trusted = .{ .id = "trust.user" },
1053 } },
1054 .{ .class = .trusted_extern, .authority = .{
1055 .trusted = .{ .id = "trust.extern" },
1056 } },
1057 .{ .class = .trusted_environment, .authority = .{
1058 .trusted = .{ .id = "trust.environment" },
1059 } },
1060 };
1061 inline for (premises) |premise| {
1062 try std.testing.expect(validatePremise(PremiseTestSource, premise) == null);
1063 }
1064 }
1065
1066 test "premise declarations reject unknown obligations" {
1067 const premise = Premise{
1068 .class = .theorem_domain,
1069 .authority = .{ .theorem = .{ .key = "missing" } },
1070 };
1071 try std.testing.expectEqual(
1072 Violation.premise_obligation_unknown,
1073 validatePremise(PremiseTestSource, premise).?,
1074 );
1075 }
1076
1077 test "claim declaration envelope excludes typed binding coordinates" {
1078 const digest = envelopeDigest(sourceView(PremiseTestSource));
1079 comptime var changed = PremiseTestSource;
1080 changed.overload.detail = "changed overload intent";
1081 const changed_digest = envelopeDigest(sourceView(changed));
1082 try std.testing.expect(!std.mem.eql(u8, &digest, &changed_digest));
1083 }
1084
1085 test "weak capacity owners use the canonical declaration API" {
1086 const Warm = struct {};
1087 const Dynamic = struct {};
1088 comptime {
1089 declareWarmRetained("test.warm", Warm);
1090 declareDynamicUnbounded("test.dynamic", Dynamic);
1091 }
1092 }