AppSurface Search
Example

Named canary adoption lab

Source of truth

View source Edit this page

Last updated

This local lab shows the complete boundary around a protected AppSurface named canary: your application triggers a workflow and records its evidence, AppSurface evaluates that existing evidence, and your caller chooses what to do with the bounded appsurface canary poll result.

A pass proves one consumer-defined workflow produced acceptable bound evidence at one point in time. It is not health, readiness, traffic rollout analysis, general application safety, automatic production approval, or customer readiness. You choose which features, dependencies, inputs, and stress conditions make the workflow meaningful for your release.

First local proof (POSIX)

Prerequisites: a restored repository checkout, the .NET 10 SDK, Bash, and curl. The verifier starts the local lab, creates ephemeral values without printing them, triggers the application-owned workflow, and calls the source CLI.

bash examples/named-canary-lab/verify.sh pass

Expected safe terminal result:

PASS canary=lab.proof attempts=1 elapsed=...
Named-canary lab 'pass' scenario verified safely.

The verifier is a POSIX convenience path, not a new product surface. It builds the lab and source CLI before starting the local host, cleans up the local child process, and does not print credentials, markers, marker fingerprints, endpoint bodies, application payloads, or local logs. If the local host cannot start or bind, the trigger fails, or the CLI returns an unexpected terminal result, it preserves the local diagnostic log and prints only its path; inspect and remove that file locally, and do not share its contents. It supplies trigger headers through curl's standard input, so they are not expanded into curl process arguments. It allows up to two minutes for the loopback bind after that build, but stops earlier when the child process exits.

What runs where

your caller                         your application                    AppSurface
-----------                         ----------------                    ----------
set release policy                  protected trigger                   protected GET route
set marker + freshness       --->   bound proof store            --->   evaluator + envelope
poll CLI exit code                  candidate/environment check         no trigger, no deploy

The trigger and the named-canary route use the same local operator policy in this lab. It deliberately models one local development operator, not a multi-operator production permission design. The trigger is application-owned. appsurface canary poll is read-only: it never starts the workflow, changes traffic, deploys, rolls back, or selects a CI platform. The process-local proof cache is capped at 128 distinct markers; a full cache returns a bounded 429 response and should be cleared by restarting the lab rather than treated as rollout health. Trigger markers follow the named-canary marker profile, so an accepted proof is always pollable with the same marker.

Manual walkthrough

Use this path when you want to inspect the integration shape. It keeps every secret-bearing value in environment variables. Run it in one terminal so the background lab and caller share the same local values.

POSIX shell

(
set -eu
set +x
export NAMED_CANARY_LAB_OPERATOR_TOKEN="$(LC_ALL=C od -An -N 32 -tx1 /dev/urandom | tr -d '[:space:]')"
export NAMED_CANARY_LAB_MARKER="local-marker-$(date +%s)-$$"
export NAMED_CANARY_LAB_FRESH_SINCE="$(date -u +%Y-%m-%dT%H:%M:%SZ)"

lab_pid=""
cleanup() {
  if [ -n "$lab_pid" ] && kill -0 "$lab_pid" 2>/dev/null; then
    kill "$lab_pid" 2>/dev/null || true
    wait "$lab_pid" 2>/dev/null || true
  fi
}
trap cleanup 0 INT TERM

ASPNETCORE_ENVIRONMENT=Development \
NamedCanaryLab__OperatorToken="$NAMED_CANARY_LAB_OPERATOR_TOKEN" \
NamedCanaryLab__Candidate="local-candidate" \
NamedCanaryLab__Environment="development" \
NamedCanaryLab__Scenario="Pass" \
dotnet run --project examples/named-canary-lab/NamedCanaryLab.csproj -- --port 61260 &
lab_pid=$!

startup_deadline=$((SECONDS + 120))
while ! curl --disable --noproxy '*' --connect-timeout 1 --max-time 1 \
  --silent --show-error --fail http://127.0.0.1:61260/ >/dev/null 2>&1; do
  if [ "$SECONDS" -ge "$startup_deadline" ]; then
    echo "The named-canary lab did not become reachable before the local deadline." >&2
    exit 1
  fi

  sleep 0.1
done

curl --disable --noproxy '*' --connect-timeout 1 --max-time 5 \
  --silent --show-error --fail \
  --request POST \
  --config - \
  --output /dev/null \
  http://127.0.0.1:61260/lab/canary/trigger <<EOF
header = "Authorization: Bearer $NAMED_CANARY_LAB_OPERATOR_TOKEN"
header = "X-AppSurface-Canary-Marker: $NAMED_CANARY_LAB_MARKER"
EOF

APPSURFACE_CANARY_TOKEN="$NAMED_CANARY_LAB_OPERATOR_TOKEN" \
APPSURFACE_CANARY_MARKER="$NAMED_CANARY_LAB_MARKER" \
dotnet run --project Cli/ForgeTrust.AppSurface.Cli/ForgeTrust.AppSurface.Cli.csproj -- \
  canary poll \
  --url http://127.0.0.1:61260 \
  --name lab.proof \
  --bearer-token-env APPSURFACE_CANARY_TOKEN \
  --marker-env APPSURFACE_CANARY_MARKER \
  --fresh-since "$NAMED_CANARY_LAB_FRESH_SINCE" \
  --timeout 5s \
  --interval 100ms \
  --no-github-summary
)

PowerShell

$env:NAMED_CANARY_LAB_OPERATOR_TOKEN = [guid]::NewGuid().ToString("N")
$env:NAMED_CANARY_LAB_MARKER = "local-marker-$([guid]::NewGuid().ToString('N'))"
$env:NAMED_CANARY_LAB_FRESH_SINCE = [DateTimeOffset]::UtcNow.ToString("O")
$env:ASPNETCORE_ENVIRONMENT = "Development"
$env:NamedCanaryLab__OperatorToken = $env:NAMED_CANARY_LAB_OPERATOR_TOKEN
$env:NamedCanaryLab__Candidate = "local-candidate"
$env:NamedCanaryLab__Environment = "development"
$env:NamedCanaryLab__Scenario = "Pass"
$lab = Start-Process dotnet -PassThru -ArgumentList "run --project examples/named-canary-lab/NamedCanaryLab.csproj -- --port 61260"

$ready = $false
for ($attempt = 0; $attempt -lt 1200 -and -not $ready; $attempt++) {
  try {
    Invoke-WebRequest -UseBasicParsing http://127.0.0.1:61260/ | Out-Null
    $ready = $true
  }
  catch {
    Start-Sleep -Milliseconds 100
  }
}

try {
  if (-not $ready) {
    throw "The named-canary lab did not become reachable before the local deadline."
  }

  Invoke-WebRequest -UseBasicParsing -Method Post `
    -Headers @{ Authorization = "Bearer $env:NAMED_CANARY_LAB_OPERATOR_TOKEN"; "X-AppSurface-Canary-Marker" = $env:NAMED_CANARY_LAB_MARKER } `
    -Uri http://127.0.0.1:61260/lab/canary/trigger | Out-Null

  $env:APPSURFACE_CANARY_TOKEN = $env:NAMED_CANARY_LAB_OPERATOR_TOKEN
  $env:APPSURFACE_CANARY_MARKER = $env:NAMED_CANARY_LAB_MARKER
  dotnet run --project Cli/ForgeTrust.AppSurface.Cli/ForgeTrust.AppSurface.Cli.csproj -- `
    canary poll `
    --url http://127.0.0.1:61260 `
    --name lab.proof `
    --bearer-token-env APPSURFACE_CANARY_TOKEN `
    --marker-env APPSURFACE_CANARY_MARKER `
    --fresh-since $env:NAMED_CANARY_LAB_FRESH_SINCE `
    --timeout 5s `
    --interval 100ms `
    --no-github-summary
}
finally {
  if (-not $lab.HasExited) {
    Stop-Process -Id $lab.Id
  }
}

The curl or Invoke-WebRequest trigger acknowledgement is deliberately discarded. Do not print the named-canary HTTP response either: it is a protected protocol envelope and contains a marker fingerprint. Use the CLI result as the safe caller surface.

Deterministic local scenarios

Select the scenario at application startup, before triggering the workflow. It cannot be selected by a route, query string, request body, header, or cookie. Restart the lab or choose a new marker between scenarios.

Scenario Startup configuration Safe terminal result Caller next action
pass NamedCanaryLab__Scenario=Pass exit 0 Continue only under your own release policy.
pending NamedCanaryLab__Scenario=Pending ASCAN406, exit 6 after caller deadline Verify the trigger and evidence timing, then retry under caller policy.
stale NamedCanaryLab__Scenario=Stale ASCAN403, exit 3 Produce fresh evidence after the caller freshness boundary.

For a quick local check, run bash examples/named-canary-lab/verify.sh pending or bash examples/named-canary-lab/verify.sh stale. Those commands succeed only when the expected non-pass exit code is observed safely.

Safe troubleshooting

What happened Safe CLI outcome What to do next Do not print
Fresh matching proof exit 0 Apply your own release policy. marker, token, proof record, endpoint body
No current proof before deadline ASCAN406, exit 6 Verify your trigger completed and evidence is bound to this candidate/environment. headers, marker, raw request, retry trace
Stale proof ASCAN403, exit 3 Create fresh proof after the caller boundary. old proof content, marker fingerprint, correlation identifier
Application-owned failure ASCAN403, exit 3 Investigate through your protected operations surface. payload, exception text, unbounded logs
Authorization or protocol problem ASCAN404, exit 4 Correct the host policy or request shape. credential, authorization header, response body
Local host does not start or bind verifier exit 3 Review and remove its retained local diagnostic log. the log contents, credential, marker, request headers
Trigger failure or unexpected CLI exit verifier exit 4 Review and remove its retained local diagnostic log. the log contents, credential, marker, request headers

The existing CLI writes a bounded diagnostic, terminal result, next action, and documentation link. It does not render marker or credential values. Keep shell tracing disabled around secret-bearing commands and configure request logging so it does not capture the authorization or canary-marker headers.

Copy the pattern, not the lab

The sample is intentionally Development-only. It fails outside Development, uses process-local records, and has no durability, eviction, multi-instance coordination, deployment orchestration, telemetry, or retry policy beyond the existing CLI.

Lab component Replace it with in your application
Local bearer-token handler Your host-owned authentication scheme and deploy-operator policy.
Local process dictionary A bounded or evicting durable proof source that binds candidate/version, environment, authorized producer, marker, and observed time.
Startup scenario The real synthetic, integration, browser, or load workflow that exercises the release risk you chose.
CanaryLabEvaluator Your application-owned evaluator, following the complete Web fixture.
Shell exit branch Your deployment system’s policy for continuing, retrying, investigating, approving, or rolling back.

This lab adds no server or CLI protocol and does not install a CI integration. The public protocol and terminal-result contract remain the Web named-canary guide and CLI poll reference.