Addresses the T20 security code-review findings:
- Gate template script + alarm Expression-trigger bodies too (they compile and
execute at the site), not just script bodies — EnumerateTrustGatedScripts now
extracts and vets the {"expression":"..."} C# via the same trust validator.
- Per-origin local-declaration exclusion: a helper declared in a template script
no longer suppresses a genuinely-missing reference of the same name in an
ApiMethod (a global set silently defeated the ApiMethod hard-blocker).
- Fail-closed: a ScriptTrustValidator throw on one pathological body is caught
and surfaced as a hard blocker FOR THAT script (apply) / Blocker row (preview)
instead of aborting the whole import.
- Corrected the misleading attribute-Value comment (typed literal, not a compiled
expression) and documented the Pass-0 fail-fast rationale.
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
#05-T23. ArtifactDiff.CompareTemplate gains optional folderNameById /
templateNameById maps; FolderNameOf / BaseTemplateNameOf / CompositionTargetNameOf
resolve the existing template's FolderId / ParentTemplateId / composition
ComposedTemplateId to real names (falling back to the <id:N> placeholder only
when a map is absent or misses, keeping existing unit-test callers valid).
PreviewAsync builds templateNameById from GetAllTemplatesAsync (parent/composition
targets can be any template, not just bundle-named ones) and threads both maps
through. Fixes spurious Modified on every re-import of a foldered/derived/composed
template. End-to-end preview test + two ArtifactDiff unit tests.
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
#05-T20. Bundle import is now the fifth ScriptTrustValidator delegating call
site. RunSemanticValidationAsync gains a Pass 0 that runs ScriptTrustValidator
.FindViolations over every non-Skip template / shared / ApiMethod script body
before name resolution; any violation is a HARD error for all kinds (a trust
verdict is authoritative, not the false-positive-prone name heuristic) and
short-circuits so it is never masked by a Pass-1 name error. DetectBlockersAsync
runs the same gate for preview parity, emitting entity-qualified Blocker rows.
Transport gains a direct ScriptAnalysis project reference. Docs: Component-
ScriptAnalysis (fifth call site) + Component-Transport updated.
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
#05-T19. Import name-resolution findings are now split by origin: template-script
(and attribute-default-expression) references become non-blocking advisory
warnings (ConflictKind.Warning + ImportResult.Warnings) since the design-time
deploy gate re-validates authoritatively; ApiMethod-script references stay hard
errors (SemanticValidationException / ConflictKind.Blocker) as they have no
downstream gate. Names declared locally in a body (Roslyn-parsed local functions
/ methods) are excluded so a script's own helper isn't flagged; KnownNonReferenceNames
extended with common BCL/LINQ/SQL surface. DetectBlockersAsync and
RunSemanticValidationAsync Pass 1 apply the identical split so preview and apply
agree. Rollback/hard-fail tests retargeted to ApiMethods (the still-hard-failing
vehicle). CentralUI preview renders the new Warning badge/advisory.
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
#05-T8. Seeds a maximally-populated instance of every transported entity type,
exports via the real IBundleExporter, imports into a fresh target via the real
IBundleImporter (LoadAsync→ApplyAsync, Add path; site/instance types via a
BundleNameMap CreateNew), reloads, and compares by reflection — every public
writable scalar/string property must survive, except an explicit per-type
exclusion list (surrogate Id, remapped FK ids, inheritance-placeholder flags,
and the documented Instance.State→NotDeployed design reset).
23 entity types across 10 [Fact]s. 7 green. 3 red — each surfaces a genuine
residual fidelity hole whose serializer-level unit test is GREEN, proving the
loss is in the full pipeline (exactly the gap class this integration guard
exists to catch), reported for a separate production fix rather than masked:
- ExternalSystemDefinition.MaxRetries / RetryDelay dropped on import Add
- ExternalSystemMethod rows dropped on import Add (only Overwrite syncs them)
- NotificationRecipient rows dropped on export (FindAsync, no Include)
- TemplateFolder.ParentFolderId hierarchy flattened on import Add
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
New Commons seam: ScriptArtifactsChanged record + ScriptArtifactKinds + the
IScriptArtifactChangeBus pub/sub interface. Host ships InProcessScriptArtifactChangeBus
(lock-free copy-on-write, swallow-and-log per subscriber) registered as a central-role
singleton. BundleImporter gains an optional IScriptArtifactChangeBus? and, AFTER
tx.CommitAsync, publishes one notification per script-bearing kind (ApiMethod/
SharedScript/Template) whose resolution was Overwrite/Rename, using post-resolution
names — fully guarded so a bad subscriber can't fail a committed import. Contract +
plan-06 handoff table documented. No consumer yet (additive; publisher is correct).
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
Follow-up from the T5 code review: assert an idempotent Overwrite emits zero
TemplateNativeAlarmSource* audit rows, and round-trip IsInherited/LockedInDerived
as true through both the serializer and the importer BuildTemplate path.
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
New TemplateNativeAlarmSourceDto + init-only TemplateDto.NativeAlarmSources (empty
default, additive — IsInherited placeholders carried for the collision detector).
Exported in ToBundleContent, consumed in FromBundleContent + BuildTemplate, and a
new SyncTemplateNativeAlarmSourcesAsync runs the Overwrite add/update/delete child
sync with per-change audit rows. ArtifactDiff.CompareTemplate now DiffChildren over
native sources so Preview reports a NativeAlarmSources field change. Proven by an
end-to-end flatten test: an instance native-alarm-source override that dangled
pre-fix now resolves against the re-imported template source.
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
Adds a trailing optional `bool LockedInDerived = false` to TemplateAttributeDto,
TemplateAlarmDto and TemplateScriptDto (mirrors the ExecutionTimeoutSeconds
additive precedent — no schemaVersion bump). Populated at export
(ToBundleContent), consumed on import (FromBundleContent, BuildTemplate, and all
three SyncTemplate*Async changed-predicate + copy + new-entity paths, incl. audit
payloads). Old-form bundles that lack the property still deserialize to false.
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj