Phase 3 completion: prompt safety, AI output validation, queue telemetry, deploy artifacts #2

Merged
hannah-vernon merged 2 commits from feature/phase3-completion into dev 2026-08-18 17:35:06 -05:00

What does this PR do?

Completes the remaining Phase 3 skeleton work: prompt-injection-resistant prompt assembly, strict AI output validation, queue telemetry with traffic-light evaluation (D-0012), and initial deployment artifacts.

  • PromptAssembler (D-0018): renders SYSTEM / APPLICATION / UNTRUSTED-OBSERVED-DATA sections. Untrusted values appear only inside data blocks delimited by a per-assembly cryptographically random boundary, so observed content cannot forge a closing delimiter or escape its block. Template placeholders can never resolve to untrusted variables (hard error). Oversized values are truncated with an explicit marker; malicious variable names are sanitized.
  • ClassificationOutputValidator (D-0018): strict, fail-closed validation of raw model output using only System.Text.Json (no third-party schema dependency). Required fields and ranges enforced; unknown top-level properties rejected (e.g., a model-invented execute_command field fails validation); oversized output rejected; failures never carry partial data.
  • Queue telemetry (D-0012): QueueTelemetrySnapshot domain record, IQueueTelemetryStore port + in-memory implementation, IQueueStatsSource surfaced by IWorkQueue, QueueTelemetryPublisher background worker, and QueueHealthEvaluator deriving green/amber/red from oldest-pending age, depth, dead letters, and telemetry staleness (stale telemetry itself is red). Threshold defaults are placeholders pending Hannah's approval (tracked in TODO.md).
  • deploy/: Dockerfile.pipeline, Dockerfile.admin (multi-stage, non-root), sanitized docker-compose.example.yml (file-mounted secrets per D-0006, no inbound ports on the pipeline), .dockerignore. Not verified: no container tooling on the dev workstation; verification deferred to the Debian VM per Hannah's decision.

Fixes #

How was this tested?

  • dotnet build Viegard.slnx - 0 errors, 0 warnings
  • dotnet test Viegard.slnx - 70/70 tests pass (37 new: prompt-injection containment, boundary-forgery resistance, unique-boundary-per-assembly, untrusted-placeholder rejection, validator strictness incl. unknown-property and oversized-output rejection, traffic-light derivation incl. stale-telemetry-is-red)
  • Manually tested: pipeline host boots; telemetry publisher starts (10s interval)
  • Container builds NOT tested (no Docker on this workstation; deferred to Debian VM)

Checklist

  • I have read the Contributing Guide
  • Changes are focused - one logical change per PR
  • Documentation updated (AGENT-README.md, TODO.md, DECISIONS.md D-0018)
  • New dependencies are MIT/Apache-2.0 and security-vetted (none added)
  • No secrets, credentials, or real infrastructure identifiers in the diff
  • No commented-out code or debug leftovers
## What does this PR do? Completes the remaining Phase 3 skeleton work: prompt-injection-resistant prompt assembly, strict AI output validation, queue telemetry with traffic-light evaluation (D-0012), and initial deployment artifacts. - **PromptAssembler** (D-0018): renders SYSTEM / APPLICATION / UNTRUSTED-OBSERVED-DATA sections. Untrusted values appear only inside data blocks delimited by a per-assembly cryptographically random boundary, so observed content cannot forge a closing delimiter or escape its block. Template placeholders can never resolve to untrusted variables (hard error). Oversized values are truncated with an explicit marker; malicious variable names are sanitized. - **ClassificationOutputValidator** (D-0018): strict, fail-closed validation of raw model output using only System.Text.Json (no third-party schema dependency). Required fields and ranges enforced; unknown top-level properties rejected (e.g., a model-invented `execute_command` field fails validation); oversized output rejected; failures never carry partial data. - **Queue telemetry** (D-0012): `QueueTelemetrySnapshot` domain record, `IQueueTelemetryStore` port + in-memory implementation, `IQueueStatsSource` surfaced by `IWorkQueue`, `QueueTelemetryPublisher` background worker, and `QueueHealthEvaluator` deriving green/amber/red from oldest-pending age, depth, dead letters, and telemetry staleness (stale telemetry itself is red). Threshold defaults are placeholders pending Hannah's approval (tracked in TODO.md). - **deploy/**: `Dockerfile.pipeline`, `Dockerfile.admin` (multi-stage, non-root), sanitized `docker-compose.example.yml` (file-mounted secrets per D-0006, no inbound ports on the pipeline), `.dockerignore`. **Not verified**: no container tooling on the dev workstation; verification deferred to the Debian VM per Hannah's decision. Fixes # ## How was this tested? - [x] `dotnet build Viegard.slnx` - 0 errors, 0 warnings - [x] `dotnet test Viegard.slnx` - 70/70 tests pass (37 new: prompt-injection containment, boundary-forgery resistance, unique-boundary-per-assembly, untrusted-placeholder rejection, validator strictness incl. unknown-property and oversized-output rejection, traffic-light derivation incl. stale-telemetry-is-red) - [x] Manually tested: pipeline host boots; telemetry publisher starts (10s interval) - [ ] Container builds NOT tested (no Docker on this workstation; deferred to Debian VM) ## Checklist - [x] I have read the [Contributing Guide](../CONTRIBUTING.md) - [x] Changes are focused - one logical change per PR - [x] Documentation updated (AGENT-README.md, TODO.md, DECISIONS.md D-0018) - [x] New dependencies are MIT/Apache-2.0 and security-vetted (none added) - [x] No secrets, credentials, or real infrastructure identifiers in the diff - [x] No commented-out code or debug leftovers
- PromptAssembler: SYSTEM/APPLICATION/UNTRUSTED sections; untrusted values
  only inside per-assembly random-boundary data blocks; placeholders can
  never resolve to untrusted variables; oversized values truncated with
  marker; malicious variable names sanitized (D-0018)
- ClassificationOutputValidator: strict fail-closed validation of model
  output on System.Text.Json; required fields/ranges enforced, unknown
  properties rejected, oversized output rejected, no partial data on
  failure (D-0018)
- Queue telemetry (D-0012): QueueTelemetrySnapshot domain record,
  IQueueTelemetryStore port + in-memory impl, IQueueStatsSource on
  IWorkQueue, QueueTelemetryPublisher worker, QueueHealthEvaluator with
  configurable green/amber/red thresholds (defaults pending approval)
- deploy/: Dockerfile.pipeline, Dockerfile.admin, sanitized compose
  example, .dockerignore.  NOT verified: no container tooling on this
  workstation; verification deferred to the Debian VM per Hannah
- Tests: 70 passing (37 new: prompt-injection containment, boundary
  forgery resistance, validator strictness, traffic-light derivation)
- Docs: AGENT-README enforcement points + state, TODO Phase 3 status,
  DECISIONS D-0018

Verified: dotnet build (0 warnings), dotnet test (70/70), pipeline host
boots with telemetry publisher running.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
hannah-vernon/viegard-sentinel!2
No description provided.