Commit Graph

2144 Commits

Author SHA1 Message Date
Joseph Doherty c60347c5e7 fix(transport): blocker heuristic no longer hard-blocks valid imports — local declarations excluded, denylist extended, template-script findings downgraded to warnings
#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
2026-07-10 02:12:26 -04:00
Joseph Doherty 8e92198078 docs(archreview): sync PLAN-05 tasks.json — Wave 6 complete (T6, T8, T14 → 20/25)
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-09 17:44:58 -04:00
Joseph Doherty 7c74bbe4b5 fix(transport): close 5 residual import/export fidelity holes surfaced by the round-trip guard (T8)
The Task-8 reflection round-trip suite exposed five silent-data-loss paths in
non-template entity types:
- ExternalSystemDefinition.MaxRetries/RetryDelay dropped on import Add (and Overwrite
  never updated them) — BuildExternalSystem + Overwrite branch now carry both.
- ExternalSystemMethod rows dropped on import Add — a new post-flush
  PersistAddedExternalSystemMethodsAsync pass adds them (the entity has no parent
  nav, so an Add can't cascade; idempotent vs the inline Overwrite sync).
- NotificationRecipient dropped on export — GetNotificationListByIdAsync now
  Include(Recipients) instead of a bare FindAsync.
- TemplateFolder.ParentFolderId flattened on import Add — a new
  ResolveFolderParentEdgesAsync second-pass rewire resolves ParentName→id,
  mirroring the template inheritance/composition rewires.

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-09 17:44:12 -04:00
Joseph Doherty e498c6e1bd test(transport): per-entity reflection round-trip equivalence suite — structural guard against silent import data loss
#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
2026-07-09 17:37:32 -04:00
Joseph Doherty 2c839def33 feat(commons/transport): ScriptArtifactsChanged invalidation contract + post-commit publish from bundle import (consumer lands in plan 06)
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
2026-07-09 17:27:40 -04:00
Joseph Doherty 38edfefc42 refactor(transport): single-source template child equality — diff and overwrite-sync can no longer drift
New TemplateChildEquality is the one place "has this template child changed?" is
decided, comparing the COMPLETE writable field set per entity (attributes now incl.
ElementDataType + LockedInDerived; alarms incl. the on-trigger script by name +
LockedInDerived; scripts incl. MinTimeBetweenRuns/ExecutionTimeoutSeconds +
LockedInDerived; native sources per T5). ArtifactDiff.CompareTemplate and all four
BundleImporter.SyncTemplate*Async predicates now route through it — closing the
class of bug where Preview reported Identical for an artifact an Overwrite would
mutate. The attribute sync passes a value-normalised DTO so List-value re-imports
stay idempotent; the alarm on-trigger change now surfaces in both diff and sync.

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-09 17:17:18 -04:00
Joseph Doherty 33cf577718 docs(archreview): sync PLAN-05 tasks.json — Wave 5 complete (T5 → 17/25)
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-09 17:06:17 -04:00
Joseph Doherty 7b6d6bb805 test(transport): harden T5 native-source coverage — no-change zero-audit + IsInherited/LockedInDerived fidelity
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
2026-07-09 17:05:02 -04:00
Joseph Doherty e4136cb920 fix(transport): transport template NativeAlarmSources — imports no longer amputate native alarm mirrors nor dangle instance overrides
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
2026-07-09 16:59:37 -04:00
Joseph Doherty 4df5109742 docs(archreview): sync PLAN-05 tasks.json — Wave 4 complete (T4, T11, T12 → 16/25)
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-09 16:48:20 -04:00
Joseph Doherty d833219920 fix(template-engine): DiffService covers native alarm sources — Deployments diff view no longer blind to native-alarm edits
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-09 16:46:45 -04:00
Joseph Doherty 0302694f9a fix(template-engine): native alarm sources participate in the revision hash — staleness detection restored (deliberate one-time stale flip for affected instances)
TemplateEngine-011: ResolvedNativeAlarmSource bindings were absent from the
flattened-config revision hash, so edits to a native alarm source binding
(source reference, connection, condition filter, locked state) went undetected
by staleness detection. Add a HashableNativeAlarmSource record and fold the
sorted, null-when-empty NativeAlarmSources collection into HashableConfiguration
in its alphabetical slot. WhenWritingNull keeps every native-source-free config
byte-identical to its pre-migration hash (pinned literal test proves it), so the
migration is surgical: only native-source-bearing instances flip stale once
after upgrade; redeploy clears it — deliberate.

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-09 16:46:45 -04:00
Joseph Doherty bf17f60a04 fix(transport): transport LockedInDerived on template attributes/alarms/scripts (additive DTO fields)
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
2026-07-09 16:46:33 -04:00
Joseph Doherty ea9c5709ac docs(archreview): sync PLAN-05 tasks.json — Wave 3 complete (T10, T13, T18 → 13/25)
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-09 16:31:45 -04:00
Joseph Doherty 874e32a93e fix(template-engine): collision detection sees all slots of a repeatedly-composed template
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-09 16:29:00 -04:00
Joseph Doherty 57f7f65772 fix(security): enforce template locks on native alarm source overrides at flatten and management-command level
Add IsLocked to ResolvedNativeAlarmSource; FlatteningService skips overrides on a
locked source (mirrors attribute/alarm lock rules); ManagementActor SetInstanceNativeAlarmSourceOverride
flattens the instance and rejects an override on a locked source (and rejects a
canonical name that does not resolve, preventing dangling overrides).

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-09 16:28:52 -04:00
Joseph Doherty ab6077708f fix(deployment): delete NotDeployed instances centrally without a site round-trip
A NotDeployed instance has no live Instance Actor at any site, so Delete is a pure
central-side record cleanup. Transport-imported instances always land NotDeployed
(often against an uncommissioned/unreachable site); a site round-trip made them
undeletable until the site came online. Short-circuit the site call for NotDeployed.

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-09 16:22:29 -04:00
Joseph Doherty 83294ab44e docs(archreview): sync PLAN-05 tasks.json — Wave 2 complete (T3, T7, T15, T17, T22 → 10/25)
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-09 16:15:35 -04:00
Joseph Doherty 53ce64ba79 fix(transport): cap LineDiffer input size — oversized script diffs degrade to summary instead of O((N+M)^2) memory
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-09 16:13:05 -04:00
Joseph Doherty ef59c752ba perf(template-engine): cache script compile verdicts by code hash — unchanged scripts compile once per process
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-09 16:13:05 -04:00
Joseph Doherty f40f52950e fix(transport): export instance AreaName — Area-by-name reconciliation was half-shipped (exporter hardcoded null)
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-09 16:13:05 -04:00
Joseph Doherty 721a2e595f fix(deployment): audit-write fault can no longer flip a committed Success deployment to Failed
Route all post-terminal-status lifecycle audit writes (Deploy/Disable/Enable/Delete/DeleteOrphaned) through a swallow-and-warn TryLogAuditAsync helper so a failed best-effort audit write can neither reach the outer catch (flipping Success to Failed) nor propagate to the caller.

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-09 16:12:56 -04:00
Joseph Doherty 5c52b4c80b fix(transport): carry script cadence/timeout fields through bundle import (was silently reset to defaults)
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-09 16:09:50 -04:00
Joseph Doherty 4d4ae05fc2 docs(archreview): sync PLAN-05 tasks.json — Wave 1 complete (T2, T9, T16, T21 → 5/25)
Parallel worktree-isolated implementers cherry-picked onto the plan branch;
combined full build 0/0 and 8 impacted consumer suites green (shared-component
changes to ValidationService + ScriptTrustValidator re-verified end-to-end).

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-09 15:55:03 -04:00
Joseph Doherty aae0a2db3e fix(transport): reject bundle imports that would persist an inheritance/composition cycle
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-09 15:49:55 -04:00
Joseph Doherty 854a6a8c0b perf(deployment): staleness/comparison paths no longer Roslyn-compile every script — deploy gate remains the authoritative compile
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-09 15:49:55 -04:00
Joseph Doherty 730bf19162 fix(template-engine): repeated composition no longer drops nested members — cycle guard keyed on recursion path, not global template id
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-09 15:49:55 -04:00
Joseph Doherty d19ab93d39 fix(security): deny Environment/GC and ADO.NET provider namespaces; close reflection-gateway list (GetTypes/EntryPoint/Declared*/DynamicInvoke)
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-09 15:49:55 -04:00
Joseph Doherty dbd3ed2fc7 docs(archreview): sync PLAN-04 tasks.json + tracker — Task 26 sweep GREEN, plan COMPLETE (26/26)
Full-solution build 0 warn/0 err; all 30 unit+integration test assemblies pass
after fixing one stale enum-count lock-in test (22d52390). Playwright E2E residue
is environmental (stale docker cluster, SQLite-open on site-container deploy;
non-deterministic set; zero PLAN-04 file overlap). Initiative total 104/192.

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-09 14:59:20 -04:00
Joseph Doherty 22d5239039 test(commons): update AuditKind lock-in test for 15th member (ReconciliationAbandoned)
Task 17 (durable record for permanently-abandoned reconciliation rows) added
AuditKind.ReconciliationAbandoned but did not update the enum member-count
lock-in test AuditEnumTests, which asserted exactly 14 members. The per-task
filtered test runs did not include this Commons enum guard, so the gap surfaced
only in the PLAN-04 Task 26 full-solution sweep — exactly what the sweep exists
to catch. Rename to *FifteenExpectedMembers, add ReconciliationAbandoned to the
expected set, bump the count assertion to 15.

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-09 14:57:28 -04:00
Joseph Doherty a9da1ed804 docs(archreview): sync PLAN-04 tasks.json + tracker — Task 24 completed (25/26, only T26 sweep remains) 2026-07-09 10:05:24 -04:00
Joseph Doherty 7f1b59ff84 docs: reconcile design docs with shipped code (enums, Sms, per-channel retention, SiteCalls schema, conventions)
PLAN-04 Task 24. Component-Commons: AuditChannel gains SecuredWrite (5), AuditKind full
15-value list (+SecuredWrite*/ReconciliationAbandoned), NotificationType Email/Sms (Teams
evaluated+dropped), AuditLogRow persistence-only-projection deviation note. ConfigDB:
Type discriminator Email/Sms, data-layer conventions (UTC DateTime + Utc converter, MSSQL-
only repos/fixtures, AuditLog clustered-key + P3/P4/P6 tracked follow-ups). SiteCallAudit:
schema table rewritten to shipped columns (Channel/Target/AuditStatus-Status/SourceNode/
HttpStatus/IngestedAtUtc; no Kind/TargetSummary/provenance) + stored-vs-tracking-view note.
AuditLog: per-channel retention config lists all 5 channels incl SecuredWrite. Also fixed
two stale 'Teams planned' refs surfaced by the verification grep (HighLevelReqs, CentralUI).
2026-07-09 10:03:13 -04:00
Joseph Doherty 3a21e77c03 docs(archreview): add cross-plan parallelization map for incomplete tasks + sync stale PLAN-04 status
Corrects the Status Board (PLAN-04 24/26, not 0), and adds a Parallelization Map to
00-MASTER-TRACKER plus a per-plan Parallelization block to PLAN-04/05/06/07/08. Derives
ready sets from the live .tasks.json blockedBy graphs and the Files metadata, identifies
the initiative-wide serialization mutexes (ManagementActor.cs single-writer, EF migration
snapshot, Host/Program.cs, ArtifactDeploymentService.cs, SiteStorageService.cs,
NotificationOutboxActor.cs, AlarmSummary.razor), and recommends the safe concurrent
subagent lanes per plan. Flags PLAN-07 T33 as a dedupe-skip (CLI.Tests already in slnx).
2026-07-09 09:53:05 -04:00
Joseph Doherty 05e36d8665 docs(archreview): sync PLAN-04 tasks.json — Tasks 22, 23, 25 completed (Batch 8, purge first-tick + data-layer cleanups + bounded parallel delivery) 2026-07-09 09:43:42 -04:00
Joseph Doherty 6567216e14 perf(notification-outbox): bounded parallel delivery within a dispatch sweep (default 4) 2026-07-09 09:41:48 -04:00
Joseph Doherty acd8a6b8b5 chore(data-layer): low-severity cleanups — SQLite param chunking, KPI metric catalog, computed-column predicate guard 2026-07-09 09:32:04 -04:00
Joseph Doherty b58d810dbe fix(purge): short first tick on all three purge timers so daily-recycled nodes still purge 2026-07-09 09:26:42 -04:00
Joseph Doherty 7cd62933a6 docs(archreview): sync PLAN-04 tasks.json — Tasks 19-21 completed (Batch 7, KpiSample batched purge + NotificationOutbox single-query KPI + SiteCalls OPTION RECOMPILE) 2026-07-09 09:13:51 -04:00
Joseph Doherty 34337a8714 perf(site-call-audit): OPTION (RECOMPILE) on the optional-parameter query page 2026-07-09 09:12:50 -04:00
Joseph Doherty 6f5d60070c perf(notification-outbox): single-query KPI aggregation; oldest-pending no longer materializes the live queue 2026-07-09 09:12:00 -04:00
Joseph Doherty 71189298a2 perf(kpi-history): time-sliced batched purge replaces the single daily mega-DELETE 2026-07-09 09:06:42 -04:00
Joseph Doherty 5332912f26 docs(archreview): sync PLAN-04 tasks.json — Tasks 16-18 completed (Batch 6, reconciliation keyset cursor + abandoned-row audit + NodeB failover) 2026-07-09 08:58:39 -04:00
Joseph Doherty bfd2cc7e6f fix(audit-log): reconciliation pulls fail over to site NodeB when NodeA is unreachable
SiteEntry gains an additive FallbackGrpcEndpoint; SiteEnumerator now picks NodeA
else NodeB as primary (a NodeB-only site is no longer skipped) and carries a
distinct NodeB as the fallback. Both GrpcPull{AuditEvents,SiteCalls} clients dial
the primary first and, on a transport fault (Unavailable/DeadlineExceeded/
Cancelled/HttpRequestException/SocketException/OperationCanceled), fail over to
NodeB once before collapsing to empty — a mapping/unexpected fault does NOT fail
over. Keeps the reconciliation loss-recovery net available during a NodeA outage.
2026-07-09 08:57:06 -04:00
Joseph Doherty 0385942c3f feat(audit-log): permanently-abandoned reconciliation rows leave a durable ReconciliationAbandoned audit record
When a pulled AuditEvent fails to insert on every retry up to the permanent-
abandon threshold, the reconciliation actor now writes ONE synthetic
ReconciliationAbandoned row (new AuditKind) alongside the Critical log line —
fresh EventId, Status=Failed, channel preserved from the lost row, Extra carrying
the abandoned EventId + source site + final error — via the same
ScadaBridgeAuditEventFactory the ingest path uses. Best-effort in its own
try/catch so it never blocks the cursor twice. The permanent loss is now
queryable in the Audit Log, not only in a rotating log file.
2026-07-09 08:50:44 -04:00
Joseph Doherty 20098c6108 fix(site-call-audit): composite keyset cursor eliminates the single-timestamp reconciliation pin; pinned state now a published event
Reconciliation cursor becomes composite (UpdatedAtUtc, TrackedOperationId);
IPullSiteCallsClient/GrpcPullSiteCallsClient forward the Task-15 after_id keyset
(additive param, null preserves the legacy inclusive >= contract). A burst
sharing one exact UpdatedAtUtc now drains via the id tiebreak instead of pinning
forever. A legacy site that ignores after_id is latched + published as
SiteCallReconciliationPinnedChanged on the EventStream (transition-only),
replacing the prior silent log line.
2026-07-09 08:44:07 -04:00
Joseph Doherty 1f4c0b67ca docs(archreview): sync PLAN-04 tasks.json — Tasks 13-15 completed (Batch 5, operator-identity audit + reconciliation keyset) 2026-07-09 08:32:06 -04:00
Joseph Doherty a608d7a79b feat(site-call-audit): additive (UpdatedAtUtc, TrackedOperationId) keyset in the reconciliation pull contract
Site side: additive proto after_id field, ReadChangedSinceAsync gains an optional
afterId cursor and deterministic (UpdatedAtUtc, TrackedOperationId) ordering — a
batch fully inside one UpdatedAtUtc instant no longer re-reads the same page
forever. Absent/empty afterId preserves the exact legacy inclusive >= contract, so
an older central is unaffected. Regenerated the checked-in gRPC C#.
2026-07-09 08:31:42 -04:00
Joseph Doherty 2c45c3238b feat(site-call-audit): relay Retry/Discard emit operator-identity audit rows
Additive RequestedBy on Retry/DiscardSiteCallRequest, plumbed from the Central UI.
On an Applied relay, SiteCallAuditActor emits one best-effort CachedResolve central
direct-write row (Submitted/Discarded) with the operator as Actor. Gated on an
injected ICentralAuditWriter (null in existing tests → no mirror read, unchanged);
the site remains the source of truth for the state change.
2026-07-09 08:21:41 -04:00
Joseph Doherty 50bb1ef8ab feat(notification-outbox): operator Retry/Discard emit audit rows with operator identity
Additive RequestedBy on Retry/DiscardNotificationRequest, plumbed from the Central
UI. RetryAsync emits a NotifyDeliver Submitted row (records who un-parked); Discard
stamps the operator on the Terminal row. Best-effort — audit failure never aborts
the action.
2026-07-09 08:12:47 -04:00
Joseph Doherty f2f196aa6d docs(archreview): sync PLAN-04 tasks.json — Tasks 10-12 completed (Batch 4, backlog KPI + SiteCalls freshness + EF retry) 2026-07-09 08:04:11 -04:00