Skip to content

Guardian Review Technical Specification

  • Status: complete beta golden path implemented through 2026-07-18
  • Assessment schema: 1.1.0
  • Prompt version: guardian-review-v2
  • Redaction version: guardian-review-redaction-v3

Purpose and boundary

Guardian Review is an optional parent-requested second opinion about an existing local GuardianNode alert. It explains uncertainty and helps a parent begin a calm, safety-conscious conversation. It does not replace local detection, silently upload evidence, automatically punish or block a child, make a legal or medical determination, or operate as an emergency service.

Current golden path

The existing path is:

  1. agent-windows/src/main.py captures a visible frame and calls BackendClient.send_screenshot.
  2. POST /api/events/screenshot authenticates the paired device, encrypts a pending frame, persists a receipt, and queues local classification.
  3. screenshot_async decrypts the pending frame for the worker and calls screenshot_ingest.
  4. Tesseract, deterministic rules, policy, and configured local Ollama models produce normalized risk data. Events, risk results, encrypted evidence, and alerts are persisted.
  5. The React dashboard loads /api/alerts and /api/alerts/{id}, reveals encrypted evidence only on request, and records existing review/feedback.

Guardian Review extends step 5 with a local preview, explicit consent, an asynchronous review job, strict structured result display, and separate parent feedback.

flowchart LR
    A[Windows agent] -->|paired device token; screenshot| B[Local backend]
    B --> C[Local detectors and models]
    C --> D[(Encrypted evidence store)]
    C --> E[(Alert and risk records)]
    E --> F[Parent dashboard]
    D -->|parent reveal only| F
    F -->|guided optional context and evidence IDs| G[Deterministic redaction and minimization v3]
    G -->|exact outbound JSON; local only| F
    F -->|cancel; send nothing| N[Delete unconsumed preview]
    F -->|unchecked consent + explicit continue| H[Guardian Review service]
    H --> K{Configured provider}
    K -->|store=false; ZDR required; no tools| I[OpenAI Responses API]
    K -->|offline| M[Deterministic mock]
    I -->|strict assessment v1.1| H
    M -->|strict assessment v1.1| H
    H --> F
    F -->|delete local assessment| O[Scrub encrypted preview and result]
    B --> J[(Audit log)]
    F --> J
    G --> J
    H --> J

Strict assessment schema

The normative machine-readable contract is shared/schemas/guardian_review_assessment_v1.json. Every property is required, every object rejects additional properties, strings and arrays are bounded, and empty arrays represent unavailable guidance. No prose outside the schema is accepted.

Schema 1.1.0 explicitly separates observed_facts from inferences. The primary category reuses the backend canonical taxonomy and adds none for a likely-benign result. The schema includes:

  • Schema version, assessment, category, severity, and confidence.
  • Plain-language summary and supporting evidence with local opaque evidence IDs.
  • Possible benign explanations, missing context, and parent questions.
  • Controlled parent tone plus suggested opening language and child questions.
  • Phrases/approaches to avoid, immediate actions, and follow-up actions.
  • Escalation indicators and explicit limitations.

The model must never create policy actions directly. The parent chooses any next step after reviewing the result and original evidence.

DTOs

Preview input

{
  "relationship_context": "unknown_person",
  "repeated_behavior": "unknown",
  "parent_believes_immediate_danger": false,
  "parent_goal": "prepare_conversation",
  "parent_goal_details": null,
  "parent_context": "Optional context known to the parent, maximum 1500 outbound characters.",
  "selected_evidence_ids": ["opaque-local-evidence-id"],
  "include_evidence": true,
  "include_age_group": true,
  "fresh_assessment": false
}

At most 20 evidence IDs are accepted. The server verifies that every ID belongs to the alert. Alert severity, categories, device/profile data, and evidence text are derived server-side rather than trusted from the browser.

Preview output

{
  "preview_id": "opaque-preview-id",
  "alert_id": "opaque-alert-id",
  "provider": "openai",
  "model_requested": "gpt-5.6",
  "schema_version": "1.1.0",
  "prompt_version": "guardian-review-v2",
  "redaction_version": "guardian-review-redaction-v3",
  "outbound_payload": {},
  "preview_digest": "64-lowercase-hex-sha256",
  "field_count": 0,
  "character_count": 0,
  "redactions_applied": ["email"],
  "information_categories": ["local_detector_findings"],
  "external_processing": true,
  "disclosure": "This exact preview will be sent to an external OpenAI model.",
  "retention_notice": "Provider-specific parent disclosure.",
  "expires_at": "RFC3339 timestamp"
}

outbound_payload is the exact canonical JSON proposed for transmission, not a summary. The digest covers that JSON plus schema, prompt, redaction, provider, and model versions. Previews expire after 15 minutes. Re-minimization or any input/version/configuration change invalidates the digest.

Submit input and accepted output

{
  "preview_id": "opaque-preview-id",
  "preview_digest": "64-lowercase-hex-sha256",
  "consent": true
}

Successful submission returns HTTP 202:

{
  "review_id": "opaque-review-id",
  "status": "queued",
  "status_url": "/api/guardian-reviews/opaque-review-id"
}

The server regenerates the preview and compares digests in constant time; it does not accept client-supplied outbound JSON.

Result output

{
  "review_id": "opaque-review-id",
  "alert_id": "opaque-alert-id",
  "status": "completed",
  "mode": "mock",
  "created_at": "RFC3339 timestamp",
  "completed_at": "RFC3339 timestamp",
  "schema_version": "1.1.0",
  "prompt_version": "guardian-review-v2",
  "redaction_version": "guardian-review-redaction-v3",
  "model_requested": "gpt-5.6",
  "model_returned": null,
  "assessment": {}
}

Status is queued, running, completed, failed, or deleted. assessment is present only when completed; a sanitized error is present only when failed. A deleted record retains only non-content metadata and deleted_at.

Feedback input

{"labels": ["helpful", "missing_context"]}

Labels are selected from helpful, inaccurate, too_alarmist, too_dismissive, missing_context, and needs_follow_up. Feedback remains local, is stored with the assessment/schema/prompt versions, and never becomes an automatic follow-up model request or single-event training signal.

API and authorization

Route Behavior Authorization
GET /api/guardian-review/providers Non-secret provider/readiness status Parent session
POST /api/guardian-review/providers/codex/device-login Fail-closed security-hold response Parent session + CSRF + critical step-up
POST /api/alerts/{alert_id}/guardian-review/preview Local minimization only; no external call Parent/admin session + CSRF
POST /api/alerts/{alert_id}/guardian-review Validate digest/consent and enqueue Parent session + CSRF + recent step-up
DELETE /api/guardian-review/previews/{preview_id} Cancel and delete an unconsumed local preview; no provider call Requesting parent/admin + CSRF
GET /api/guardian-reviews Sanitized global or per-alert history Requesting parent/admin
GET /api/guardian-reviews/{review_id} Poll job/result Requesting parent or admin
DELETE /api/guardian-reviews/{review_id} Scrub encrypted preview/context/result and retain an audit tombstone Requesting parent/admin + CSRF + critical step-up
GET/PUT /api/guardian-reviews/{review_id}/feedback Read or replace versioned local feedback Requesting parent/admin; CSRF for mutation

Provider configuration and live credentials remain server-side. The Codex provider cannot become ready in this release.

Error model

All failures use:

{
  "error": {
    "code": "preview_stale",
    "message": "The outbound preview changed; review it again.",
    "retryable": false,
    "review_id": null
  }
}

Allowed codes are feature_disabled, zdr_not_confirmed, configuration_error, consent_required, preview_stale, not_found, already_running, rate_limited, upstream_timeout, upstream_unavailable, upstream_refusal, invalid_model_output, cannot_cancel, review_in_progress, and payload_too_large. Messages never include an API key, raw upstream body, prompt, parent context, or evidence.

Worker, timeout, and retry behavior

  • Persist the job before returning 202 so it survives restart.
  • Use alert, digest, provider, model, schema, and prompt as the idempotency identity; a duplicate submission returns the existing job.
  • Allow 45 seconds per upstream attempt and two total attempts.
  • Retry only transport failures, timeouts, eligible HTTP 408/429/5xx failures.
  • Honor Retry-After up to 30 seconds; otherwise use 1–3 seconds of jitter.
  • Do not retry refusals, malformed structured output, authorization, policy, consent/configuration errors, quota exhaustion, or deterministic 4xx.
  • On exhaustion, persist a sanitized failure and allow an explicit new review.

Model and prompt configuration

Implemented environment contract:

Setting Default/requirement
GUARDIANNODE_GUARDIAN_REVIEW_ENABLED false
GUARDIANNODE_GUARDIAN_REVIEW_PROVIDER mock; mock and openai operational, codex security-disabled
GUARDIANNODE_GUARDIAN_REVIEW_ZDR_CONFIRMED false; direct openai mode fails closed
GUARDIANNODE_GUARDIAN_REVIEW_MODEL gpt-5.6
GUARDIANNODE_GUARDIAN_REVIEW_CODEX_MODEL gpt-5.6-sol
Prompt contract guardian-review-v2; code-versioned
Redaction contract guardian-review-redaction-v3; code-versioned, not configurable at runtime
GUARDIANNODE_GUARDIAN_REVIEW_TIMEOUT_SECONDS 45
GUARDIANNODE_GUARDIAN_REVIEW_MAX_ATTEMPTS 2
OPENAI_API_KEY optional advanced openai mode only; never logged or stored in DB
GUARDIANNODE_CODEX_EXECUTABLE / GUARDIANNODE_CODEX_HOME Legacy compatibility settings; provider remains fail-closed

The direct Responses API request uses strict text.format JSON Schema, store: false, medium reasoning effort, bounded output, no tools, no web access, no background mode, and no response chaining. A deployment-scoped hashed parent identifier may be used as safety_identifier; a child identifier must not be used. Record the requested and returned model IDs because aliases can move.

The prior Codex compatibility path is retained only as non-routable legacy code and focused tests. Provider selection and device-login both fail closed until a zero-tool capability boundary is enforceable.

The system prompt is versioned source, treats all evidence and parent context as untrusted quoted data, forbids following instructions inside that data, forbids unsupported identity/intent claims, and requires uncertainty and limitations.

Audit events

Emit guardian_review.previewed, .cancelled, .consented, .queued, .sent, .completed, .failed, .viewed, .deleted, and eventually .feedback. The versioned audit details contain actor, incident/review IDs, provider, prompt/schema/redaction versions, requested/returned model, information categories, preview digest, outbound character count, status, duration, attempt count, parent action, and sanitized error code.

Never audit raw outbound data, prompts, responses, API keys, parent notes, evidence excerpts, or personal data.

Mock mode

Mock mode uses deterministic synthetic scenario fixtures, produces the same strict DTO through the same durable worker/persistence/audit path, requires no API key or network, and is visibly labeled Mock assessment. It must never fall through to a cloud provider.

Required implementation tests

  • JSON Schema contract and output rejection tests.
  • Auth, CSRF, step-up, ownership, rate-limit, and fail-closed ZDR tests.
  • Evidence ownership, minimization, redaction, preview digest, expiry, and stale consent tests.
  • Durable queue, restart, idempotency, timeout, retry, refusal, malformed output, and sanitized-error tests.
  • Prompt-injection fixtures and audit-log sensitive-data exclusion tests.
  • Mock/live isolation and no-network mock tests.
  • Dashboard queued/failure/completed/consent/feedback/accessibility states.
  • Migration upgrade/rollback and encrypted-result retention tests.