Verified: The tag model
Each claim the The tag model page makes, with the tests that
prove it in the last green CI run: 44 of 45 verified.
The claims live in docs/claims/tag-model.yaml.
Last green CI run 16 Oct 2026, 14:48 UTC on c7618d3 · all runtime claims
TAG-001 verified
A tag declared in nautilus.yaml is in scope in every PROGRAM without a VAR_EXTERNAL declaration, typed by its type: (or by what its init: implies), and a program binds only the tags it names.
- pass
TestManifestTagsAreImplicitininternal/project - pass
TestImplicitTagsBindOnlyWhereUsedininternal/project - pass
TestElementaryTagTypesininternal/project - pass
TestManifestTagsAreImplicitInLadderFBDAndSFCininternal/project(5 of 5 passed) - pass
TestInitImpliesTimeAndStringForImplicitTagsininternal/project
TAG-002 verified
Naming a tag in a program binds it but does not create it; a tag exists only once one of four writers has written it: a seed (exists from scan one), a driver input, an operator write through the HMI or POST /api/tags to a declared tag (an undeclared name is refused), or the program assigning it (a coil write creates the tag).
TAG-003 verified
Reading a tag nobody has ever written stops the scan with "undefined tag <name>" instead of running on a silent zero.
- pass
TestScanReadOfUnwrittenTagFaultsinruntime
TAG-004 verified
An output tag seeded with Init (or any seeded setpoint/state) exists from scan one, so a seal-in latch that reads its own coil does not fault on the first scan.
- pass
TestTagDefsinruntime - pass
TestPumpSealInHysteresisinacceptance - pass
pump seals in below the start level and drops out above itintestdata/projects/heated-tank
TAG-005 verified
A tag with neither a type: nor an init: that a program names without a declaration is refused with a message telling the author to give it a type or declare it in VAR_EXTERNAL.
- pass
TestUntypedTagSaysGiveItATypeininternal/project - pass
TestCheckGoldenUntypedTagincmd/naut
TAG-006 verified
A VAR_EXTERNAL declaration wins for its program but must agree with a stated type: (VAR_EXTERNAL Speed : BOOL against type: REAL is an error at the declaration). A tag with only an init: takes the declared type, and an init: that is not a value of it (TRUE for a REAL, 2.5 for a DINT) is an error naming the tag.
- pass
TestExplicitDeclarationMustAgreeWithTypedTagininternal/project - pass
TestProgramsDisagreeingOnAnUntypedTagininternal/project - pass
TestDeclarationTypesAnInitOnlyTagininternal/project
TAG-007 verified
Programs that bind one untyped tag with different types are an error naming both tasks, and a type: on the tag settles it.
- pass
TestProgramsDisagreeingOnAnUntypedTagininternal/project
TAG-008 verified
A program's own local with a tag's name shadows the tag in that program (the tag is neither read nor written there), and naut check warns "local X shadows the project tag X".
- pass
TestLocalShadowsTagininternal/project - pass
TestCheckGoldenShadowWarningincmd/naut - pass
TestLocalShadowIsWhatTheProgramReadsininternal/project
TAG-009 verified
Function blocks and functions do not see project tags implicitly; a block reaches a tag only through its own VAR_EXTERNAL.
- pass
TestFunctionBlocksDoNotSeeTagsImplicitlyininternal/project - pass
TestTagModelFunctionsDoNotSeeTagsImplicitlyininternal/project
TAG-010 verified
A file-level VAR_GLOBAL block in a library .st (a GVL) declares globals for every program; a global the manifest does not declare becomes a state tag starting at the zero of its type, a redeclaration must agree in type, and VAR_GLOBAL CONSTANT entries are constants rather than tags.
- pass
TestGVLComposesIntoEveryProgramininternal/project - pass
TestGVLRedeclarationMustAgreeininternal/project - pass
TestCheckGoldenGVLincmd/naut - pass
TestGVLGlobalsManifestSuppliesMetadataAndConstantsAreNotTagsininternal/project
TAG-011 verified
runtime.Options.Tags states each tag's role once (Input, Output, Setpoint, State with Init/Desc/Unit), composes with the flat Inputs/Outputs/Seed/Meta fields, and an Input is deliberately not seeded.
- pass
TestTagDefsinruntime - pass
TestRuntimeDrivesOutputsinruntime - pass
TestTypedInputStaysUnseededininternal/project - pass
TestTagDefsComposeWithFlatFieldsinruntime
TAG-012 verified
type: accepts every IEC elementary type, a project TYPE (UDT or enumeration) or an ARRAY[lo..hi] OF either, and a typed setpoint or state tag with no init: starts at the zero of its type.
- pass
TestElementaryTagTypesininternal/project - pass
TestTypedTagSeedsZeroOfTypeininternal/project - pass
TestEnumTypedTagininternal/project - pass
TestSeededRolesAcceptTypeInsteadOfInitininternal/project - pass
TestArrayOfProjectTypesAndZeroOfTypeValueByValueininternal/project
TAG-013 verified
type: and init: must agree: type: INT with init: 2.5 is a load error naming the tag and its type.
- pass
TestTagTypeAndInitMustAgreeininternal/project
TAG-014 verified
A type: naming something that is neither an IEC elementary type nor a project TYPE is refused, listing the types the project does declare.
- pass
TestUnknownTypeIsAnErrorininternal/project
TAG-015 verified
A function-block type is refused as a tag type, since an instance is program state, not a tag; so is a type that holds one: an array of a standard or project block at any nesting, and a TYPE whose STRUCT has a block member. A manifest tag with no type: that a program declares as one of these is refused at load the same way.
- pass
TestTagTypeAndInitMustAgreeininternal/project - pass
TestProjectFunctionBlockIsRefusedAsTagTypeininternal/project - pass
TestTypeHoldingAFunctionBlockIsRefusedAsTagTypeininternal/project - pass
TestResolveTypeNameRefusesFunctionBlocksInsideinlang/st - pass
TestProgramDeclaringAManifestTagAsAFunctionBlockIsRefusedininternal/project
TAG-016 verified
GET /api/meta reports each tag's declared type beside its desc, plus an enumeration's members; program locals get the same entry under locals; a struct lists members only where an enumeration sits beneath; and the stream carries an enumerated value as its member name.
TAG-017 verified
An init: on an untyped tag is seeded as the type the programs declare for it (init: 0 on a DINT binds an integer; init: 65 on a REAL stays a REAL), and a tag no program declares seeds a number as a REAL.
- pass
TestIntegerTagSeedsAsTheProgramDeclaresItininternal/project - pass
TestRealTagWithIntegerLiteralStaysRealininternal/project - pass
TestUndeclaredTagWithIntegerLiteralSeedsRealininternal/project
TAG-018 verified
A seeded integer tag is born to a Sparkplug host as an Int64 and a REAL as a Double.
- pass
TestIntegerTagSeedsAsTheProgramDeclaresItininternal/project - pass
TestRealTagWithIntegerLiteralStaysRealininternal/project - pass
TestNBIRTHBornTypesOfSeededIntegerAndRealTagsinsparkplug
TAG-019 verified
A UDT-typed manifest tag seeds to the zero of the type by default, and for setpoint/state tags init: may be a (nested) mapping of member names to values; members the mapping omits stay zero.
- pass
TestStructInitSeedsNestedMembersOthersZeroininternal/project - pass
TestStructDefaultSeedIsZeroMemberByMemberAndArrayLowerBoundininternal/project
TAG-020 verified
TIME members seed from an ST literal, a bare duration (90s, 500ms) or whole milliseconds, and ARRAY members seed from a list element by element from the lower bound, leaving unnamed elements zero.
- pass
TestTimeMembersSeedFromInitininternal/project - pass
TestStructDefaultSeedIsZeroMemberByMemberAndArrayLowerBoundininternal/project
TAG-021 verified
An unknown member name in a struct init: is a load error naming the tag and member path with a did-you-mean hint, and a struct-typed tag given a scalar init: is an error.
- pass
TestStructInitUnknownMemberIsAnErrorininternal/project - pass
TestSeedFromInitUnknownNestedMemberNamesThePathinlang/ir - pass
TestStructInitScalarOnStructIsAnErrorininternal/project
TAG-022 verified
POST /api/tags accepts a dotted member path (to any depth) and read-modify-writes the whole struct, leaving other members unchanged.
TAG-023 verified
An object value on POST /api/tags merges onto the tag: members it does not name keep their current values (unlike init:, which zero-fills).
TAG-024 verified
A member write never creates anything: an unknown tag, a misspelled member (with a did-you-mean) or a member path into a scalar tag is a 400 carrying the reason, not a new top-level tag.
TAG-025 verified
A member write keeps the leaf's type (a number lands as REAL or as an integer according to the member) and a type mismatch is an error, not a silent retype.
TAG-026 verified
An enumerated member takes its member name (any case, Mode#Run qualified, or its integer), and a name that is no member is a 400 listing the members.
TAG-027 verified
An array element is addressed as [n] counted from the array's declared lower bound, an index outside the bounds is a 400 giving them, and a list value merges onto an array element by element.
TAG-028 verified
The role rule is the root tag's: a member of a driver-owned input is refused, while setpoint, state and output roots are writable.
TAG-029 verified
GET /api/meta reports "memberWrites": true so an HMI can detect member-path support.
- pass
TestMetaAdvertisesMemberWritesinserver
TAG-030 partly verified
Dotted and indexed paths also address tags in a test's given:/expect:, in a force, in the built-in dashboard's tag table (each leaf of a writable struct tag is editable), and through the HMI kit's rt.writeTag.
- pass
TestGivenAndExpectAddressFieldsinacceptance - pass
TestGivenEnumMemberByPathinacceptance - pass
TestForceStructMemberinruntime - pass
TestForceEnumMemberByPathinserver - pass
TestDashboardWritableStructLeavesAreEditableByDottedPathinserver
TAG-031 verified
Runtime.OnScan observers fire once per main-task Scan(), after the program has run, see the store as that scan left it, run in registration order, and work under a virtual clock.
TAG-032 verified
A panic in an OnScan observer is recovered and logged without taking the scan down, and the returned cancel func unregisters (idempotently).
TAG-033 verified
Tags.ReadPath reads a dotted member path (scalar leaf or struct sub-tree) from inside an OnScan callback without deadlocking.
TAG-034 verified
server.hmi serves a built static SPA at / in place of the dashboard, falling back to the bundle's index.html for unmatched non-/api paths, with the built-in dashboard moved to /_nautilus/ and /api/* untouched.
- pass
TestHMIServesStaticFileinserver - pass
TestHMISPAFallbackinserver - pass
TestHMIDoesNotShadowAPIinserver - pass
TestHMIMovesBuiltinDashboardToNautilusPrefixinserver - pass
TestHMIBuiltinAssetsMoveWithDashboardinserver - pass
TestUnmatchedAPIPathIs404inserver(3 of 3 passed) - pass
TestHMISPAFallbackSurvivesAPI404inserver
TAG-035 verified
The server.hmi path is relative to the project and must resolve inside it.
- pass
TestHMIDirLoadsIntoServerOptionsininternal/project - pass
TestHMIPathMustStayInsideProjectininternal/project
TAG-036 verified
naut build embeds the HMI build output in the binary like any project file, and the built binary serves it.
TAG-037 verified
When the server.hmi directory is not built yet, neither naut run nor naut build fails; both warn and fall back to the built-in dashboard.
- pass
TestRunBuildWarnsOnMissingHMIDirincmd/naut - pass
TestHMIBannerSuffixHMIMissingWarnsincmd/naut - pass
TestHMIDirNeedNotExistAtLoadininternal/project - pass
TestServerFallsBackToDashboardWhenHMIDirMissingininternal/project
TAG-038 verified
naut build prints a warning when the embedded project comes out over about 50 MB.
- pass
TestBuildWarnsWhenEmbeddedProjectIsOver50MBincmd/naut
TAG-039 verified
A driver input must be listed in the allowlist (the manifest tag with role: input); a driver cannot create arbitrary tags by delivering extra names.
- pass
TestDriverCannotCreateUnlistedTagsinruntime
TAG-040 verified
Every stored value carries the generation at which it last changed, and writing a value equal to the one already stored does not advance the counter.
TAG-041 verified
Tags.SnapshotInto refills the caller's map with Sample{Value, Gen} instead of allocating a fresh copy, and ChangedSince(gen, dst) appends exactly the tags that moved and returns the generation to ask with next.
- pass
TestSnapshotIntoinruntime - pass
TestChangedSinceReportsOnlyWhatMovedinruntime - pass
TestChangedSinceAccumulatesAcrossTicksinruntime - pass
TestChangedSinceReusesTheCallersBufferinruntime - pass
TestChangedSinceZeroIsEverythinginruntime - pass
TestSnapshotIntoRefillsTheCallersMapWithoutAllocatinginruntime
TAG-042 verified
WriteOutputs is called on the first scan, on any scan where an output differs from the last value handed over, after a failed write and after a redundancy takeover, and skipped otherwise; Options.AlwaysWriteOutputs restores a per-scan call.
TAG-043 verified
A driver implementing io.BatchReader is handed the same map each scan via ReadInputsInto, and the runtime falls back to ReadInputs when it is absent.
- pass
TestBatchReaderGetsTheSameMapEachScanAndPlainFallsBackinruntime
TAG-044 verified
Change detection is conservative: two struct values are equal only if they share the same StructDef, and a function-block instance is never equal to anything; it can report a change that did not happen but never hide one.
TAG-045 verified
Quality travels beside the tags: the non-good entries appear in /api/state and every stream frame as a quality map, an absent name means good, and a driver-bound input whose last read failed is stale.