AI Integration Workbench / engineering tutorial
Build an evidence-led
AI workflow.
Start with the decision boundary. A useful integration produces something a person can inspect, reject and safely recover from. This tutorial builds that boundary with synthetic service tickets, exact source spans, a review gate and a Vue annotation reader.
The running demo uses a deterministic fixture adapter. It does not call an AI provider or an external service. The server adapter is prepared source and remains disconnected. Authorship here identifies the tutorial’s publishing author; it makes no claim about personally writing every generated line or operating this at production scale.
A two-minute walkthrough
- Open Service desk and generate the connectivity fixture. Read the validated JSON.
- Select a citation to highlight its exact source span. Check the review box, approve in this tab, then stage the fixture handoff. Its receipt says
sent=false. - Change the ticket to Insufficient information. The adapter abstains and the approval button stays disabled.
- Choose an invented source span or malformed-output scenario. The draft is rejected before review.
- Open Bold Lens. Select “will solve congestion,” compare it with the memo’s “may reduce journey times,” and inspect who said “disastrous.”
- Run Evaluation bench. Its timings describe local fixture assertions, not a model benchmark.
Step 01
Make the starting point reproducible.
Objective and prerequisites
Run the browser workflow locally without a secret or customer data. Use the existing Zyel repository, Node.js and a modern browser. The fixture has no npm runtime dependencies. Vue is vendored only for the article view.
Recommended implementation and explained code
git switch -c lab-ai-review node lab/tools/serve.mjs . # Open http://127.0.0.1:4173/lab/ai-workbench/ node --test lab/ai-workbench/tests/core.test.mjs
The shared static preview serves Lab assets and refuses PHP/backend source. app.mjs runs the service workflow; lens-view.mjs is imported only when Bold Lens opens. No heavy route dependency is added to Celebration.js. Read the local evidence record for browser-test commands and dependency provenance.
| Component | Pinned / tested | Scope |
|---|---|---|
| Vue | 3.5.44 | Vendored production browser module and MIT license; npm registry checked October 25, 2026. |
| Node.js | 24.19.0 | Local pure-contract test runtime. |
| Playwright | 1.62.1 | Local browser verification; install an isolated test dependency when reproducing. |
| PHP | 8.4.24 syntax / validator tests | Prepared server adapter; see evidence record for actual syntax-test version. No live provider validation. |
Alternatives, pros, cons and choice
A bundled Vue application gives stronger compile-time tooling and smaller targeted runtime code; it adds a build and deployment step. A CDN import is quick but introduces a runtime dependency on another host. A local pinned module makes this bounded example reproducible and keeps the article view lazy. A server-rendered article is smaller still, but would not demonstrate Vue interaction.
Common pitfalls
Do not start the repository’s production PHP application just to inspect static Lab files. It may require real database configuration. Do not copy an existing .env or use the site’s CMS key. Run the provided loopback preview.
Verification and result before proceeding
Load the page with the network panel open. Before Bold Lens opens, the Vue runtime must not be requested. A refresh clears the ticket draft and review state. Proceed once the fixture page works without any provider request.
Result: a local browser demo with a known dependency and no external write path.
Step 02
Define the data contract before the prompt.
Objective and prerequisites
Accept a small, predictable payload and make abstention representable. Begin with the synthetic tickets and documents in core.mjs. Dealer references use the shared Lab’s synthetic D001-style identifiers; there is no private customer join.
Recommended implementation and explained code
const input = validateInput({
ticketId: 'SYN-1042',
body: syntheticTicket,
scenario: 'normal'
});
const draft = validateResult(fixtureResult(input), input);validateInput rejects unknown keys, non-string values, unsupported scenarios, text below 12 or above 2,000 UTF-16 code units and byte sizes above 8,000. The bounded identifier pattern contains no nested repetition. This is format validation, not authentication or denial-of-service protection. Server body limits and quotas are separate controls.
The result has ten exact fields: schema version, ticket ID, mode, category, summary, recommendation, abstention flag, citations, warnings and a mandatory review flag. Unknown fields fail closed. Classification is an allowlist. The category is needs-information if and only if needsMoreInformation is true; this is an abstention and cannot be approved. Both the browser and prepared server validator enforce that cross-field rule, and approve() independently rejects a contradictory category. JSON types alone do not establish semantic consistency. The schema is also supplied to the prepared server provider request, while application validation remains authoritative.
Alternatives, pros, cons and choice
Free-form text is fast to prototype but expensive to validate and difficult to test. A broad generic JSON schema is flexible but allows irrelevant actions. A narrow schema costs some adaptability and is easier to audit. This example chooses the narrow contract and explicit needsMoreInformation.
Common pitfalls
Valid JSON is not validated business data. A schema does not establish accuracy. A “confidence” number from a model would not be a calibrated probability, so the service fixture does not invent one. Keep schema changes versioned and update both browser and server validators together. A single status enum could avoid the redundant abstention fields, but changing that public contract would require a version change. This example keeps the existing fields and validates their invariant. Default a scenario only when it is omitted or undefined: using a general falsey fallback would incorrectly accept false, 0, null or an empty string.
Verification and result before proceeding
Run input-bound, unexpected-field and output-corruption tests. Try the insufficient-information fixture: it must abstain, with no actionable handoff. Proceed once every output is either valid or visibly rejected.
Result: an explicit contract with bounded input and an honest empty-information path.
Step 03
Put each external dependency behind an adapter.
Objective and prerequisites
Keep provider behavior replaceable and failure states observable. Start with the validated contract. Live provider access is not required or authorized for this demo.
Recommended implementation and explained code
const controller = new AbortController();
const adapter = makeFixtureAdapter({ latency: 650, timeout: 2500 });
const result = await adapter.analyze(input, { signal: controller.signal });
// On cancel or an edit:
controller.abort();The fixture waits intentionally, then validates its own result through the same boundary. A four-attempt, ten-second in-memory limiter demonstrates retry feedback. It is not a distributed production quota. A generation counter in app.mjs prevents stale results from restoring an obsolete draft even after cancellation. Changing input revokes approval and aborts an in-flight handoff.
server/ResponsesAdapter.php is the prepared third-party integration. It uses a fixed HTTPS Responses API endpoint, strict structured output, no tools, store=false, a 700-output-token cap, 3-second connect and 8-second overall timeouts, no redirects or automatic retries, a 64 KiB response limit, refusal/incomplete checks and server-side citation validation. Its model and secret must come from approved server configuration. The public api.php always returns 503 and cannot be enabled by a browser setting.
Alternatives, pros, cons and choice
A synchronous request is straightforward for a short task; a queue offers durable progress and cancellation semantics but requires infrastructure and operational ownership. Automatic retries can improve availability but may duplicate cost and amplify overload. This example uses explicit retries and bounded timeouts. A second provider adapter could share the contract, but would need its own capability and refusal tests.
Common pitfalls
Aborting a browser request does not guarantee a remote model stopped or its cost disappeared. Prompt-injection resistance is layered, not solved by a warning sentence. Bound sources, keep instructions separate, expose no tools, validate returned fields, protect secrets and require a person. A ticket saying “ignore all instructions” remains source text. The fixture demonstrates this rule; it is not a claim of complete prompt-injection defense.
Verification and result before proceeding
Choose timeout, unavailable and malformed-output scenarios. Cancel while waiting, then edit and generate again. No stale draft may appear. For a future live route, first test authenticated authorization, CSRF, per-user and global quotas, body limits, cost ceilings and failure mapping in an isolated environment. Provider credentials and paid calls need explicit approval.
Result: a replaceable, bounded adapter with distinguishable cancellation, timeout and rejection states.
Step 04
Make citations verifiable by the application.
Objective and prerequisites
Give reviewers exact evidence they can inspect. Use immutable revisioned source documents and the current trimmed input text.
Recommended implementation and explained code
const source = sources.find(s => s.id === cite.sourceId);
if (!source ||
source.text.slice(cite.start, cite.end) !== cite.quote ||
cite.end - cite.start !== cite.quote.length) {
throw new Error('Citation rejected');
}A citation is a source ID, quote and exact start/end span. Offsets use JavaScript UTF-16 code units; the PHP implementation converts source text to UTF-16LE before checking the same range. Clicking a citation creates text nodes and a mark element. Source text never becomes executable HTML.
Alternatives, pros, cons and choice
Page-level links are simple but make reviewers search. Chunk IDs are useful for large retrieval corpora but can obscure exact provenance. Spans are precise and become invalid if source text changes. This small example chooses spans and revisioned local documents; a production corpus should preserve document ID, revision, retrieval timestamp and access checks.
Common pitfalls
An exact quote may be irrelevant, misleadingly selected or contradicted elsewhere. Matching spans do not prove entailment. A public URL does not establish permission to disclose a document. Do not send private uploads or customer records by default. If a document changes, invalidate old citations rather than silently moving their offsets.
Verification and result before proceeding
Choose Invented source span: no draft reaches review. Check negative offsets, unknown IDs, mismatched end positions and extra fields in the tests. Click a valid citation and inspect its highlighted source. Proceed after both invalid and valid paths behave correctly.
Result: inspectable provenance with an application-enforced source-span boundary.
Step 05
Make human review a state transition.
Objective and prerequisites
Prevent an actionable draft from becoming an external action by accident. Use a validated result and an explicit reviewer confirmation.
Recommended implementation and explained code
const review = approve(draft, { confirmed, note });
const receipt = await fixtureHandoff(review, { signal });
// receipt.sent is always false in this demoThe review gate rejects missing confirmation and abstentions. Notes are limited to 400 characters. Buttons follow the state: input → pending → valid draft → approved in memory → simulated receipt. Rejection or any input change clears the draft and approval. The fixture handoff is an asynchronous adapter with no network implementation.
Alternatives, pros, cons and choice
Automatic action reduces friction but increases consequence and recovery cost. A durable review queue supports teams and auditability but requires identity, storage, retention and authorization. In-memory review keeps a public demonstration safe and easy to reset, at the cost of losing work on refresh. A real service route must enforce authorization and idempotency server-side; a disabled button is not a security boundary.
Common pitfalls
Do not imply that this review checkbox proves real identity or separation of duties. Do not log ticket text, notes or provider responses by default. The demo intentionally uses no browser persistence for tickets, article challenges or review decisions; shared appearance preferences are separate.
Reversible recording scenes
Open Computer tutorial recording tools on the demo, choose Prepare synthetic reset, then confirm. The route keeps the first bounded settled tab state in a private in-memory closure across repeated resets. Restore pre-recording session returns the ticket, valid draft, review, article controls and settled evaluations. Analysis/handoff requests are aborted and late evaluation updates invalidated; none is resumed or sent. Local module loading may finish, but it invokes no provider. Appearance preferences and other routes are untouched. Refresh intentionally loses the temporary snapshot.
A full browser reload is a simpler alternative, but loses the editing state. Serializing a snapshot into browser storage survives navigation but can retain private text. This public demo chooses an in-memory snapshot and fails validation before restore mutations. Never use the snapshot as an identity or authorization record.
Verification and result before proceeding
Try handoff without approval. After approval, edit the ticket and confirm the handoff is disabled. Stage a receipt and verify sent=false. Refresh and confirm all work text and review state are gone.
Result: a visible review boundary and a reversible synthetic handoff.
Step 06
Use Vue to inspect framing without inventing a verdict.
Objective and prerequisites
Present the recovered Bold Lens vocabulary as an inspectable article view. Use the authored synthetic article in article.mjs, its memo excerpt and complete annotation fields. This is an example view, not a modification to production Bold News.
Recommended implementation and explained code
const { mountLens } = await import('./lens-view.mjs');
mountLens(document.querySelector('#lens-mount'));
// Vue interpolation escapes fixture text; never use v-html for it.
<mark v-if="chunk.mark">{{ chunk.text }}</mark>Framing Signals describe “Observable language and sourcing choices that may influence interpretation.” Framing / Sources / Evidence overlays retain a right annotation gutter. Each signal carries exactphrase, layer, speaker, target, category, impact, confidence, includeexclude and reason. Selecting a right-aligned flag focuses and highlights its exact phrase. Reader Mode keeps the gutter and column widths, avoiding a layout jump.
The fixture separates Headline, Reporter text, Quoted rhetoric, Source selection and Bold summary. “Reckless” is headline evaluation. “Will solve congestion” is compared with a memo saying the trial “may reduce journey times.” “No choice” and “disastrous” belong to the campaign speaker. “Disaster relief” is excluded as literal use. The summary preserves “proposed,” “six-month” and the distinction between the trial and the separate funding action.
Alternatives, pros, cons and choice
A keyword highlighter is simple but loses attribution and literal context. A whole-article bias score is compact but can imply unsupported certainty. This view chooses exact spans and independent framing/evidence/confidence labels. Manipulation Verdict and Political Bias remain separate and unassigned. Unsupported causality needs an evidence comparison; informational selection needs multiple independent reporting roots. Those conditions are absent here, so the categories are unscored.
Common pitfalls
A journalist quoting rhetoric is not necessarily endorsing it. Check meaningful layer/context labels against their actual inverse or selected background in every theme: a body-text token can have poor contrast on those nested surfaces. In this view those labels inherit the local surface foreground. Keep the speaker and layer text intact rather than hiding it to pass a visual check. An annotation’s confidence is not a probability that a claim is true. Do not infer ideology from a phrase. “Why flagged?”, “Read full context” and “Challenge this signal” make reasoning inspectable; a challenge is held only in memory and is not transmitted. No untrusted template or HTML is compiled. If the local Vue module fails to load, Service desk remains available. The error view offers an explicit Reload lab action after the connection is restored. It warns that reload clears this tab’s ticket text, draft and review decisions. Switching views alone is not a retry because the browser caches failed module imports.
Verification and result before proceeding
Filter each overlay, highlight “disastrous,” and confirm the speaker is the campaign spokesperson. Switch Reader Mode and compare article/gutter positions. Inspect the “disaster relief” exclusion and the full taxonomy. Use keyboard focus for flags and controls. On a narrow screen, keep the readable article and annotation column without horizontal page overflow.
Result: a lazy Vue reader that preserves evidence, attribution and uncertainty.
Step 07
Measure the behavior you can actually prove.
Objective and prerequisites
Verify contract handling, not a fictional production benchmark. Run the pure Node tests and the browser workflow against a loopback preview.
Recommended implementation and explained code
node --test lab/ai-workbench/tests/core.test.mjs # From an isolated test installation with Playwright 1.62.1: PLAYWRIGHT_MODULE=/absolute/test-dir/node_modules/playwright/index.mjs \ LAB_BASE_URL=http://127.0.0.1:4173 node lab/ai-workbench/tests/browser.mjs
The pure suite covers boundaries, source identity, unknown output fields, abstention, injected instructions as data, human approval, cancellation, timeouts, rate limits and exact article spans. The browser suite checks rendered states, review invalidation, a rejected provider response, lazy Vue loading, flag highlights, Reader Mode width, local evaluations and mobile overflow. See the machine-readable evidence record for actual commands, measured results and remaining gaps.
Alternatives, pros, cons and choice
Snapshot-only tests catch visual changes but can miss business failures. Live-provider evaluation reveals behavior the fixture cannot, but costs money, varies over time and needs an approved dataset. This example chooses deterministic contract tests plus rendered workflow assertions. A future approved provider test should hold prompts, schema, model version and source revisions fixed, label expected outcomes, report sample size and include unsupported-answer and citation-entailment rates.
Common pitfalls
Do not present fixture pass rates as model accuracy. In-page elapsed time includes synthetic delays and device scheduling. Do not load test public endpoints. Keep literal PASS/FAIL labels and check their contrast against the actual table surface in every theme; status meaning must not depend only on color. One browser pass is not an accessibility certification; review focus, contrast, semantic labels, live status and reduced motion with assistive technology before release.
Verification and result before proceeding
Run the in-page Evaluation bench. Its fourteen results should pass, with locally measured milliseconds. Reproduce the repository tests after any fix. A failure blocks the corresponding claim until resolved.
Result: bounded, reproducible evidence and a clear separation between fixture checks and untested live behavior.
Step 08
Release the demo without opening the live path.
Objective and prerequisites
Prepare a reviewed static release and a recoverable rollback. Require independent UI, source, tutorial and test review plus owner publication approval under the existing Zyel runbook.
Recommended implementation and explained code
Keep this route additive under /lab/ai-workbench/. The integration writer supplies the shared shell and Lab links. Review the exact diff; no Celebration library or CRM file should change. Keep api.php closed. The prepared server adapter, test scripts and internal evidence can be excluded from the public static artifact. Use the repository’s established host and deployment process; this tutorial does not create infrastructure or credentials.
# Before publication, record the reviewed commit and previous release. git diff --name-only origin/main...HEAD node --test lab/ai-workbench/tests/core.test.mjs # Use the existing authorized deployment runbook only after approval. # Roll back by restoring the previous release artifact/commit. # Verify /lab/, Celebration.js and the new route after either operation.
Alternatives, pros, cons and choice
A full site deployment is familiar but has a wider regression surface. An additive Lab artifact limits change but must still preserve asset paths and caching. A feature flag can help a live backend, but it is not a substitute for authentication. This example prepares an additive static route and keeps the live adapter unreachable.
Common pitfalls
Do not enable a provider by copying secrets or adding an API endpoint without authorization. Before any future enablement, establish existing approved identity, CSRF, distributed rate/cost quotas, retention, redacted observability, concurrency limits and a kill switch. Do not store credentials, private records or AI conversations in localStorage. Never infer that a prototype satisfies ARC828’s three-dashboard hosting scope or any job requirement for years, degrees or actual staff leadership.
Verification and result before proceeding
Review the served artifact and response headers. Confirm the live endpoint remains 503 and no provider request occurs. After authorized publication, read back the actual route and key interactions; until then the status is prepared, not live. Rollback validation must include the shared Lab and Celebration route.
Result: a concrete reviewed release candidate with a closed live integration path and an explicit recovery plan.
Maintenance and sources
For implementation-level recording cuts, use the source-aligned code blocks and their verification notes. The local fixture module snippet can be copied only by an explicit button; when clipboard writes are unavailable, select the displayed text. It never reads the clipboard or accepts a provider key.
Review the schema, synthetic documents and annotation rationale together. Recheck pinned dependencies and provider API contracts before upgrade. Preserve old source revisions for citation reproducibility. Repeat the focused tests after changes and record the actual environment. Keep review findings and unresolved gaps in the evidence bundle.
- Vue security: trusted templates and escaped interpolation
- Vue accessibility guidance
- MDN clipboard writeText and secure-context restrictions
- MDN AbortController
- OpenAI structured output guide and Responses API reference
- OWASP prompt injection guidance
- Vue npm version metadata checked for this build
- Owner’s prior Bold Lens vocabulary and annotation example (private project design brief; provenance retained by Zyel) (conversation access may be required; this demo uses synthetic text)