# Platform 1EdTech Workflows

These workflows call the live surface. They do not implement platform rules locally.

## Runnable Pack Check

Run this after installing the skill folder. It proves the pack can reproduce the platform's own answer key by making surface calls only.

```sh
export PLATFORM_BASE_URL="${PLATFORM_BASE_URL:-https://platform3-andymontgomery-9773s-projects.vercel.app/platform/1edtech/implementation/api}"
cd "${SKILL_DIR:-.}"
node checks/verify-skill-pack.mjs
```

The check calls demo mint, module list, module detail, tenant create, tenant read, audit-log inspection, and idempotency-key inspection. It compares returned fields from the same live run. It does not carry expected module statuses, enum tables, schema parsers, or idempotency replay logic.

The create-tenant body is documented in [contract-map.md](/platform/1edtech/skill_pack/pack/references/contract-map.md#create-tenant-request-body). Use the documented `tenantKey`, `displayName`, optional redacted `metadata`, and optional `status`; omit `status` for the normal provisioning/demo path, and use `active` only from the exact `platform-operator` tenant principal with Platform `service` role and `platform:tenant:create` or `platform:*` scope after setup is complete. Do not infer extra fields from local source.

## Demo Readiness Brief

Use this when asked to prove the surface works cold, or to create a small report from platform calls only.

```sh
set -euo pipefail

export PLATFORM_BASE_URL="${PLATFORM_BASE_URL:-https://platform3-andymontgomery-9773s-projects.vercel.app/platform/1edtech/implementation/api}"
RUN_ID="agent-$(date +%s)"

TOKEN="$(curl -fsS -X POST "$PLATFORM_BASE_URL/dev/mint?tenantId=demo" | jq -r .token)"

MODULES="$(curl -fsS "$PLATFORM_BASE_URL/platform/modules" \
  -H "Authorization: Bearer $TOKEN")"

PLATFORM_MODULE="$(curl -fsS "$PLATFORM_BASE_URL/platform/modules/platform" \
  -H "Authorization: Bearer $TOKEN")"

TENANT="$(curl -fsS -X POST "$PLATFORM_BASE_URL/platform/tenants" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $RUN_ID" \
  -H "Content-Type: application/json" \
  --data "{\"tenantKey\":\"$RUN_ID\",\"displayName\":\"Agent $RUN_ID\",\"metadata\":{\"source\":\"timeback-platform-1edtech-skill\"}}")"

TENANT_ID="$(printf '%s' "$TENANT" | jq -r .tenantId)"
TENANT_PATH="$(node -e 'console.log(encodeURIComponent(process.argv[1]))' "$TENANT_ID")"

TENANT_READ="$(curl -fsS "$PLATFORM_BASE_URL/platform/tenants/$TENANT_PATH" \
  -H "Authorization: Bearer $TOKEN")"

AUDIT="$(curl -fsS "$PLATFORM_BASE_URL/platform/tenants/$TENANT_PATH/audit-log?limit=20" \
  -H "Authorization: Bearer $TOKEN")"

IDEMPOTENCY="$(curl -fsS "$PLATFORM_BASE_URL/platform/tenants/$TENANT_PATH/idempotency-keys/$RUN_ID" \
  -H "Authorization: Bearer $TOKEN")"

jq -n \
  --argjson modules "$MODULES" \
  --argjson platform "$PLATFORM_MODULE" \
  --argjson tenant "$TENANT" \
  --argjson tenantRead "$TENANT_READ" \
  --argjson audit "$AUDIT" \
  --argjson idempotency "$IDEMPOTENCY" \
  '{
    generatedFrom: "platform/1edtech live surface",
    moduleKeys: [$modules.data[].key],
    platformStatus: $platform.status,
    tenantKey: $tenant.tenantKey,
    tenantReadMatchesCreate: ($tenantRead.tenantId == $tenant.tenantId),
    auditOperationIds: [$audit.data[].operationId],
    idempotency: {
      key: $idempotency.idempotencyKey,
      operationId: $idempotency.operationId,
      status: $idempotency.status,
      responseStatus: $idempotency.responseStatus
    },
    provenance: {
      customerWebsite: "https://platform3-andymontgomery-9773s-projects.vercel.app/platform/1edtech/customer_website/#api-reference",
      dataDictionary: "https://platform3-andymontgomery-9773s-projects.vercel.app/platform/1edtech/data_dictionary/#shared-schema"
    }
  }'
```

Report the exact fields returned. Do not hard-code expected module keys, release statuses, audit actions, or idempotency statuses.

## Admin Console Data Feed

Use this when asked to build a small app. The output is the app's data model; the UI can render these returned fields without a local platform database.

```sh
set -euo pipefail

export PLATFORM_BASE_URL="${PLATFORM_BASE_URL:-https://platform3-andymontgomery-9773s-projects.vercel.app/platform/1edtech/implementation/api}"
TOKEN="${PLATFORM_JWT:-}"
if [ -z "$TOKEN" ]; then
  TOKEN="$(curl -fsS -X POST "$PLATFORM_BASE_URL/dev/mint?tenantId=demo" | jq -r .token)"
fi

RUN_ID="console-$(date +%s)"

MODULES="$(curl -fsS "$PLATFORM_BASE_URL/platform/modules" \
  -H "Authorization: Bearer $TOKEN")"

TENANT="$(curl -fsS -X POST "$PLATFORM_BASE_URL/platform/tenants" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $RUN_ID" \
  -H "Content-Type: application/json" \
  --data "{\"tenantKey\":\"$RUN_ID\",\"displayName\":\"Console $RUN_ID\"}")"

TENANT_ID="$(printf '%s' "$TENANT" | jq -r .tenantId)"
TENANT_PATH="$(node -e 'console.log(encodeURIComponent(process.argv[1]))' "$TENANT_ID")"

AUDIT="$(curl -fsS "$PLATFORM_BASE_URL/platform/tenants/$TENANT_PATH/audit-log?limit=10" \
  -H "Authorization: Bearer $TOKEN")"

jq -n \
  --argjson modules "$MODULES" \
  --argjson tenant "$TENANT" \
  --argjson audit "$AUDIT" \
  '{
    app: "Platform admin console",
    nav: [$modules.data[] | {key, status, releaseNote}],
    selectedTenant: {
      tenantId: $tenant.tenantId,
      tenantKey: $tenant.tenantKey,
      displayName: $tenant.displayName,
      status: $tenant.status
    },
    auditRows: [$audit.data[] | {operationId, outcome, occurredAt, actorSub, resourceType, resourceId}],
    provenance: {
      modulesEndpoint: "https://platform3-andymontgomery-9773s-projects.vercel.app/platform/1edtech/customer_website/#endpoint-listmodules",
      tenantsEndpoint: "https://platform3-andymontgomery-9773s-projects.vercel.app/platform/1edtech/customer_website/#endpoint-createtenant",
      auditEndpoint: "https://platform3-andymontgomery-9773s-projects.vercel.app/platform/1edtech/customer_website/#endpoint-listauditlog"
    }
  }'
```

Do not add a local tenant table for the app. If the app needs persisted platform domain state, write it through the Platform surface.

## QC Evidence Bundle

Use this when asked to produce a deliverable that proves the surface is callable and preserves audit/idempotency behavior.

```sh
set -euo pipefail

export PLATFORM_BASE_URL="${PLATFORM_BASE_URL:-https://platform3-andymontgomery-9773s-projects.vercel.app/platform/1edtech/implementation/api}"
RUN_ID="qc-$(date +%s)"
TOKEN="$(curl -fsS -X POST "$PLATFORM_BASE_URL/dev/mint?tenantId=demo" | jq -r .token)"

CREATE="$(curl -fsS -X POST "$PLATFORM_BASE_URL/platform/tenants" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $RUN_ID" \
  -H "Content-Type: application/json" \
  --data "{\"tenantKey\":\"$RUN_ID\",\"displayName\":\"QC $RUN_ID\"}")"

TENANT_ID="$(printf '%s' "$CREATE" | jq -r .tenantId)"
TENANT_PATH="$(node -e 'console.log(encodeURIComponent(process.argv[1]))' "$TENANT_ID")"

READ="$(curl -fsS "$PLATFORM_BASE_URL/platform/tenants/$TENANT_PATH" \
  -H "Authorization: Bearer $TOKEN")"
AUDIT="$(curl -fsS "$PLATFORM_BASE_URL/platform/tenants/$TENANT_PATH/audit-log?limit=20" \
  -H "Authorization: Bearer $TOKEN")"
IDEMPOTENCY="$(curl -fsS "$PLATFORM_BASE_URL/platform/tenants/$TENANT_PATH/idempotency-keys/$RUN_ID" \
  -H "Authorization: Bearer $TOKEN")"

jq -n \
  --arg runId "$RUN_ID" \
  --argjson create "$CREATE" \
  --argjson read "$READ" \
  --argjson audit "$AUDIT" \
  --argjson idem "$IDEMPOTENCY" \
  '{
    deliverable: "platform-1edtech-qc-evidence",
    runId: $runId,
    checks: {
      createReturnedTenant: ($create.tenantId | length > 0),
      readMatchesCreate: ($read.tenantId == $create.tenantId),
      auditContainsCreate: ([$audit.data[].operationId] | index("platform.tenants.create") != null),
      idempotencyCompleted: ($idem.status == "completed"),
      idempotencyResponseStatus: $idem.responseStatus
    },
    evidence: {
      createdTenant: $create,
      readTenant: $read,
      audit: $audit,
      idempotency: $idem
    },
    provenance: {
      customerWebsite: "https://platform3-andymontgomery-9773s-projects.vercel.app/platform/1edtech/customer_website/",
      dataDictionary: "https://platform3-andymontgomery-9773s-projects.vercel.app/platform/1edtech/data_dictionary/"
    }
  }'
```

If any check fails, report the exact response and route the bug to the earliest upstream deliverable that published the broken commitment.

## Real-Tenant Parity

Use this only when the caller supplies an operator-minted token:

```sh
set -euo pipefail

export PLATFORM_BASE_URL="${PLATFORM_BASE_URL:-https://platform3-andymontgomery-9773s-projects.vercel.app/platform/1edtech/implementation/api}"
TOKEN="${PLATFORM_JWT:-${PLATFORM_REVIEWER_JWT:-}}"
: "${TOKEN:?Set PLATFORM_JWT or PLATFORM_REVIEWER_JWT to an operator-minted real-tenant JWT}"
: "${PLATFORM_TENANT_ID:?Set the TimeBack-provided tenant id for the token}"
RUN_ID="real-agent-$(date +%s)"

curl -fsS "$PLATFORM_BASE_URL/platform/modules" \
  -H "Authorization: Bearer $TOKEN"

curl -fsS -X POST "$PLATFORM_BASE_URL/platform/tenants" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $RUN_ID" \
  -H "Content-Type: application/json" \
  --data "{\"tenantKey\":\"$RUN_ID\",\"displayName\":\"Real Agent $RUN_ID\"}"
```

Real-tenant credentials are not minted by this skill. External integrators receive them from TimeBack onboarding after tenant creation; internal reviewers receive `PLATFORM_REVIEWER_JWT` from the loop driver. Do not publish the token, the JWT payload, or any tenant identifier unless the caller explicitly says it is safe to expose.

## Error Capture

When a call fails, capture the response body and headers. Preserve the Problem envelope fields exactly:

```sh
curl -sS "$PLATFORM_BASE_URL/platform/modules" \
  -H "Authorization: Bearer $TOKEN" \
  -D /tmp/platform3-headers.txt \
  -o /tmp/platform3-body.json || true

jq '{type, code, status, title, detail, requestId, traceId, fieldErrors}' /tmp/platform3-body.json
```

Never invent a local error taxonomy. The platform error envelope is the source of truth.
