Building PROTO RECON — From Vague Idea to a Browser-Based Tactical HUD

Introduction

This post walks through how PROTO RECON — an experimental app that combines phone sensors with in-browser ML — evolved from pre-coding requirements to its current implementation. I’ve verified the UI behavior, but I’m still at the stage of reading the source code for the first time. Here I organize the original requirements and an AI-generated source map as a foundation for deeper dives in future posts.

Note: This is still in testing. Performance tuning for heavy workloads hasn’t been done yet — use at your own risk.

Where It Started

It began with a vague question: Can we build a digital user experience on top of the real world? The seed of the idea was whether today’s phone sensors could overlay something like the robot-view or cockpit-view perspectives from old sci-fi and games onto live scenery.

Before writing code, I summarized the requirements, turned functional specs into prose through dialogue with AI, and then started development.

Technology Choices (Before Implementation)

Area Adopted / Considered Notes
Object detection TensorFlow.js (COCO-SSD / BlazeFace) “Object recognition” as the core of the robot-view feel, fully in the browser
Map Leaflet Familiar “you are here” display from games
Noise / effects Initially GLSL / three.js Wanted to reuse past effect-building experience

The noise layer was added later. When I had Cursor implement it, three.js was skipped in favor of Canvas 2D and CSS for CRT-style effects. Writing directly without extra modules kept things lighter — a decision that has paid off on real devices.

Gyro, compass, altitude (on supported devices), GPS, and similar sensors were also in scope, wired to the HUD and minimap.

Requirements at the Time (Summary)

The planning document — built through repeated conversations with AI — was titled Tactical Recon & Guidance Terminal “PROJECT: LOCK-ON (working title)”. At that point, sensor accuracy led us to assume Phase 2 would require a Unity (native) rewrite. After coding with Cursor, however, things moved better than expected even without three.js, and the outlook shifted to Phase 1 might be achievable on the web alone.

Core Concept

  • Visuals: Monochrome green military-terminal UI, scan lines, noise
  • AI: Real-time object detection for targeting and lock-on
  • Immersion: Gyro, altitude, BEEP sounds — instrument and audio feedback
  • Future: VR-goggle HUD, gaze control (concept stage)

Plan vs. Current State

Item Plan (as of Apr 2026) Current (PROTO RECON)
Stack Next.js + Three.js + TF.js Vite + vanilla JS + TF.js (no Three.js)
Altimeter Barometric sensor (native assumed) Geolocation altitude (supported devices only)
Map (lightly specified in the plan) Leaflet minimap implemented
Missile effect Tap to fire a virtual missile Not implemented
Phase 2 Unity Barometric altitude, haptics, etc. Not started (web prototype continues)
Distance display (mostly undefined in planning) WebXR hit-test measurements only (no bbox-estimated distance)

The full planning document is collapsed below due to length.

Full planning document (PROJECT: LOCK-ON draft)

Tactical Recon & Guidance Terminal App — “PROJECT: LOCK-ON (working title)” Planning Document

This document summarizes a concept for a smartphone camera-filter app that blends retro-futuristic UI with modern AI.

1. Project Overview

A “play” app that recreates the experience of a military recon terminal from 1980s–90s sci-fi films and games, using live smartphone camera feed and AI.

2. Core Concept

  • Visuals: Monochrome green evoking old terminals. Retro feel via scan lines and noise.
  • AI interaction: Automatic targeting and lock-on through real-time object detection.
  • Immersion: Gyro, barometric sensor (altimeter), BEEP feedback.
  • Extensibility: HUD and gaze control with VR goggles (e.g. Hacosco) — concept stage.

3. Main Features (excerpt)

3.1 Video filter

  • Green overlay (night-vision goggle texture)
  • Edge bar graphs and scan indicators

3.2 AI lock-on

  • Automatic moving-object detection with green bounding-box tracking
  • SE accelerates when the target stays in the reticle center
  • Lock complete: box turns red, warning sound
  • Tap to fire a virtual missile (effect)

3.3 Sensor-linked instruments

  • Gyro: pitch and roll numeric display
  • Altimeter: relative altitude from barometric sensor (iPhone, etc.)

4. Development Phases (roadmap at the time)

  • Phase 1: Web prototype (Next.js + Three.js + TF.js). Shareable via URL.
  • Phase 2: Unity port. Barometric altitude, vibration.
  • Phase 3: VR/XR. Stereoscopic display, gaze lock-on.

5. Distribution Strategy

Dev logs on Qiita / Zenn, short-form video on social, posts on Reddit, etc.

Created: April 29, 2026 (planning stage)

How Development Proceeded

Coding was delegated to Cursor, adding features one prompt at a time until the current state. For prompts, I load the requirements document into Gemini and ask what kind of prompt would work best. I’ve checked JS behavior through the UI, but reading the source comes next. The file list and dependency diagram below were AI-generated and cleaned up for this article.

Source Layout

HTML (root level)

File Role
index.html Main screen. BIOS/splash, boot progress, camera feed, reticle/detection overlay, compass/altitude tape, minimap, CRT effects, etc. Loads /src/main.js as a module
privacy.html Privacy policy (static page: collected data, purpose of use, etc.)
oss-notice.html Static OSS / third-party license notice

src/ — runtime files

Entry, config, UI

File Role
main.js App hub. Boot sequence, camera acquisition, start/stop of modules, HUD updates, user actions (start/stop, lock, .etc.)
appConfig.js Dev tuning defaults (camera, CRT, object detection, face mask, minimap, motion linkage, live health, etc.)
applyDevConfig.js Applies devConfig.crt as CSS variables on document.documentElement (vignette, scan lines, etc.)
uiLexicon.js Single source for UI copy (splash, status, errors, minimap labels, etc.) including functions to apply text to HTML
style.css Global layout, HUD, CRT, minimap, reticle styles (green military UI)

Video & AI inference

File Role
tfBackend.js One-time TensorFlow.js backend init (WebGPU → WebGL → CPU)
personDetection.js COCO-SSD object detection. Bounding-box drawing, lock zone, target events, inference health notifications
facePrivacy.js BlazeFace face detection and masking (privacy overlay)
lockTrackKeys.js Stable IDs for targets in the lock zone (position quantization + match with previous frame)

Distance & AR

File Role
objectRange.js Distance to target on HUD — measured distances only (WebXR, etc.); no bbox-estimated distance
webxrHitTestRange.js WebXR immersive-ar + hit-test for real-world distance (m) fed into objectRange

Navigation, map, sensors

File Role
compass.js Device heading (compass) wrapper. Heading parse, cardinal labels, iOS permission request
miniMap.js Geolocation watch and minimap facade (position, heading, speed, altitude callbacks)
miniMapLeaflet.js Leaflet map init, current-location marker (heading arrow), tile display
motionHud.js Tilt β/γ from DeviceOrientation for the right vertical tape HUD
motionStabilityLink.js Links tilt/shake to reticle/minimap parallax and notifications (including vibration)

Feedback & monitoring

File Role
liveHealth.js Lightweight live health monitoring (inference latency, consecutive failures, video stall) and HUD notifications
audioFx.js Web Audio API for boot, detection, lock-on, error sounds, etc.

src/ — tests only (not used on screen)

File Role
lockTrackKeys.test.js Unit tests for lockTrackKeys.js
liveHealth.test.js Unit tests for liveHealth.js
tfBackend.test.js Unit tests for tfBackend.js

Dependencies (overview)

flowchart TB
  index[index.html] --> main[main.js]
  main --> config[appConfig / applyDevConfig / uiLexicon]
  main --> cam[camera + HUD]
  main --> det[personDetection]
  main --> face[facePrivacy]
  det --> tf[tfBackend]
  face --> tf
  main --> map[miniMap]
  map --> leaf[miniMapLeaflet]
  main --> nav[compass / motionHud / motionStabilityLink]
  main --> range[objectRange]
  range --> xr[webxrHitTestRange]
  main --> health[liveHealth]
  main --> sfx[audioFx]

main.js ties almost everything together; appConfig.js settings and uiLexicon.js copy drive behavior and display across modules.

Closing — What to Read Next

Future posts will open the source and go deep feature by feature. The study roadmap below is the planned reading order (01 is done with this article’s file map).

Source reading roadmap (32 parts — click to expand)

Stage 1 — Config and skeleton (no permissions needed)

# Title / theme Main files Core tech Common pitfalls
01 Full map — roles in src/ - Which file owns what Memorizing filenames without reading code
02 devConfig — behavior remote control appConfig.js Camera, detection, CRT key meanings One change affects the whole app
03 Single source for UI copy — uiLexicon uiLexicon.js, index.html Centralized strings and error messages Finding leftover hardcoded text
04 CRT tokens to CSS — applyDevConfig applyDevConfig.js, style.css JS config → CSS variables (:root) Typo in variable name breaks styles
05 HTML skeleton — splash & HUD DOM index.html Layer stack (video / canvas / HUD) z-index vs hidden attribute

Stage 2 ⭐⭐ — Look and boot “theater” (testable without camera)

UI animation and audio control without device permissions.

# Title / theme Main files Core tech Common pitfalls
06 Splash, BIOS, scramble text main.js, style.css First-run UI flow, timer control Duplicate timers, garbled text
07 Boot overlay — progress and steps main.js Async status during startup Failed step never transitions to error
08 Web Audio — play after click audioFx.js AudioContext and user gesture Autoplay policy, silent bugs
09 Reticle SVG and fixed layout index.html, style.css Center-fixed CSS layout Why motionStabilityLink was disabled
10 Live HUD frame and CRT noise style.css, main.js Scan-line effects over video Rendering cost of noise canvas

Stage 3 ⭐⭐⭐ — Media, sensors, tapes (real device required)

# Title / theme Main files Core tech Common pitfalls
11 Camera — getUserMedia and rear preference main.js facingMode, stream acquisition iOS permission timing, connection hang
12 Video presentation — object-fit: cover style.css, main.js Hidden → visible preview transition Unintended camera zoom
13 Full boot sequence — startCamera main.js After tap: camera → model load Failure logs when changing parallel vs sequential
14 Heading tape — continuous angle and N jump compass.js, main.js 0°–360° unwrap, SVG control Clipping at north (N), insufficient padding
15 Tilt tape — β / γ and motionHud motionHud.js, main.js DeviceOrientation deviceorientationabsolute behavior differences
16 Altitude tape — GPS altitude callback miniMap.js, main.js Geolocation API Devices where altitude is null
17 Minimap — Leaflet and OSM/CARTO miniMap.js External tile map, marker Handling location denial
18 Speed HUD — km/h from coords.speed miniMap.js Speed from coordinate changes Speed shows -- when stationary

Stage 4 ⭐⭐⭐⭐ — Detection, privacy, ML

# Title / theme Main files Core tech Common pitfalls
19 TF.js backend — WebGPU→WebGL→CPU tfBackend.js Graphics API fallback Unsupported devices, first compile, heat
20 COCO-SSD intro — ensureModel and throttling personDetection.js Model load, inference throttling CDN load failure, dynamic import()
21 Detection box drawing — drawVideoCover and canvas personDetection.js Drawing aligned to video aspect Coordinate drift, DPR
22 Lock-on — zone, dwell, key stabilization personDetection.js Tracking ID, smoothing ID swaps, flicker
23 Distance — WebXR hit-test only objectRange.js, webxrHitTestRange.js Measured distance on HUD No distance on unsupported devices
24 Face mosaic — BlazeFace and canvas compositing facePrivacy.js rAF + throttled inference Draw cycle vs inference collision
25 Mosaic interval and heat — intervalMs facePrivacy.js Inference FPS vs load tradeoff Device freeze, thermal throttling

Stage 5 ⭐⭐⭐⭐⭐ — Integration, ops, errors

# Title / theme Main files Core tech Common pitfalls
26 Live health — latency, video stall liveHealth.js Main-thread stall monitoring False positives under load spikes
27 Startup error handling — timeout and catch main.js Async error gaps Model load timeout
28 Second visit and cache vite.config.js PWA, Vite build failed to fetch dynamically imported module
29 Revisit and restart — model disposal main.js Memory leak prevention Second run extremely heavy
30 motionStabilityLink — why parallax was cut motionStabilityLink.js Parallax vs readability Janky rendering
31 Integration — main.js orchestration main.js (full) Lifecycle management Tight coupling breaks on touch
32 Retrospective — bug list and what’s next App overall Settling on load mitigation Defining “fatal bugs are gone”

The next post will start with roadmap #02 (appConfig.js).

Compliance Drift

The compliance industry was originally created to stop institutions from becoming dangerous.

That was the idea, anyway.

Too much money corrupts judgment.
Too much access corrupts restraint.
Too much unchecked authority eventually produces behavior no institution would publicly admit to intentionally designing.

So modern corporations built compliance:
frameworks,
controls,
audits,
governance boards,
risk scoring,
mandatory reporting structures,
ethics certifications,
behavioral standards.

An operational immune system designed to prevent institutions from quietly mutating into predatory environments.

And for a while, parts of it worked.

Financial controls reduced fraud.
Access reviews limited privilege abuse.
Security governance prevented catastrophic failures.
Healthcare compliance protected patient data.
Aviation compliance kept aircraft from falling out of the sky due to executive optimism and spreadsheet-based engineering.

Real compliance matters because real systems drift.

That is one of the foundational truths of cybersecurity:
every sufficiently complex environment eventually diverges from its documented secure state.

Permissions accumulate.
Exceptions multiply.
Temporary workarounds harden into permanent architecture.
Trust assumptions fossilize into invisible vulnerabilities.

The industry even has a term for it:
compliance drift.

The slow divergence between documented reality and operational reality.

Which is interesting, because many modern institutions now suffer from the exact same condition psychologically.

The paperwork says:
ethical,
inclusive,
safe,
accountable,
human-centered.

The operational environment often behaves more like politically calibrated survival infrastructure.

Not through explicit hostility.
Through strategic ambiguity.

Never fully rejecting.
Never fully accepting.
Never clearly defining the problem.
Sustaining just enough uncertainty to keep people psychologically occupied while accountability remains beautifully diffused across process, policy, and “concern.”

That ambiguity becomes operationally useful.

A person who feels fully rejected eventually disengages.
A person who feels secure gains stability.

But a person suspended between possibility and threat often continues producing while trying to resolve the uncertainty itself.

That is where things become interesting.

Because eventually the compliance officer arrives.

Every institution has one.

Expensive suit.
Controlled tone.
Carefully moderated body language.
The emotional neutrality of someone trained to convert institutional panic into procedural language.

He enters the room carrying governance vocabulary like ceremonial equipment:
policy,
alignment,
conduct,
professionalism,
expectations,
culture.

The language always sounds civilized.

That is the first warning sign.

Modern institutions rarely pressure people directly anymore. Direct coercion creates discoverable evidence. Instead they construct environments where pressure emerges beneath layers of perfectly reasonable language.

That is the real innovation of contemporary compliance culture:
the ability to operationalize discomfort without appearing operationally aggressive.

The meeting is never technically hostile.

Which is exactly why it works.

Somewhere inside a conference room with artificial plants and over-air-conditioned air, a compliance representative translates one man’s discomfort with a woman’s facial expressions into an institutional compliance concern, quietly demonstrating how easily subjective perception mutates into organizational pressure once hierarchy becomes involved.

Not illegally enough to trigger escalation.

Just enough to establish gravitational force.

That is the modern institutional specialty:
noncompliant methods deployed in defense of compliance optics.

And everyone involved understands the contradiction immediately.

Nobody says it aloud.

Because the meeting is not actually about ethics.

It is about narrative containment.

That is what large portions of the compliance industry quietly became during the AI era:
not governance infrastructure,
but liability choreography.

Meanwhile outside the conference room, institutions are deploying autonomous AI systems faster than governance departments can meaningfully interpret operational risk. Executives demand aggressive AI integration while simultaneously hosting “Responsible AI” summits assembled from slide decks, buzzwords, and optimistic forecasting nobody fully believes privately.

The infrastructure underneath many companies now resembles a probabilistic fever dream:
autonomous agents,
third-party APIs,
contractor ecosystems,
identity sprawl,
shadow AI tooling,
compliance frameworks stapled desperately onto systems nobody completely understands anymore.

And somehow compliance departments are expected to make all of this appear governable.

So the theater intensifies.

More certifications.
More workshops.
More ethics language.
More behavioral modules narrated in the emotional cadence of institutional anesthesia.

But the systems themselves continue drifting.

Technical people recognize this immediately because engineers, security analysts, and infrastructure architects spend their lives around environments pretending to be more stable than they actually are.

They develop instincts for hidden instability:
latency spikes,
unusual routing behavior,
privilege escalation,
anomalous traffic,
systems behaving differently under observation.

Human beings leak the same indicators constantly.

Which is why parts of the industry occasionally become impossible to take seriously.

An institution will quietly route a critical infrastructure effort through someone’s technical judgment to validate capability, operationalize the work immediately, redistribute ownership upward through politically safer channels, then months later joke publicly about “vibe coding” as though competence itself were merely a performative illusion.

That’s the real compliance drift.

Not failed paperwork.
Not broken governance controls.
Not missing certifications.

Institutional dishonesty normalized through hierarchy.

Because once institutions become psychologically invested in protecting authority structures, admitting where real capability originated starts feeling more dangerous than the hypocrisy required to deny it.

Compliance culture already has indirect language for behavior like this:
control without attribution.

Large institutions do it constantly.

Extract the value.
Minimize the source.
Redistribute ownership safely through politically survivable channels.
Rewrite the narrative carefully enough that dependency itself disappears from institutional memory.

Which becomes especially absurd in technology because even “vibe coding” still requires enough systems understanding to recognize when the loudest people in the room could not build the infrastructure they are mocking without borrowing someone else’s cognition first.

That is the compliance drift nobody audits:
the growing distance between institutional language and institutional behavior.

Because eventually the person across the table starts conducting an assessment too.

Not on the policies.

On the institution itself.

Which phrases were scripted by legal.
Which concerns originated from leadership panic.
Which questions are fishing expeditions.
Which moments reveal reputational fear rather than ethical concern.
Which parts of the conversation exist purely to manufacture future deniability.

By the middle of the meeting, the audit quietly reverses direction.

The compliance representative believes he is conducting an assessment.

Meanwhile the person across from him has already completed a full behavioral penetration test against the institution itself.

And the findings are rarely encouraging.

Because the real vulnerability inside most institutions is not insufficient governance.

It is the growing gap between:
what the institution claims to value,
what the institution operationally rewards,
and what frightened people inside the hierarchy become willing to rationalize in order to preserve stability.

That gap widens under pressure.

Especially now.

The political climate is unstable.
The technology industry is unstable.
AI acceleration is destabilizing labor structures faster than governance frameworks can adapt.
Entire institutions are quietly terrified they no longer fully understand the systems required to remain competitive.

Fear changes people.

It always has.

And frightened institutions become obsessed with controlling perception because perception feels easier to govern than reality.

That is why modern compliance culture increasingly feels uncanny.

The industry built to prevent institutional corruption occasionally ends up functioning like an advanced linguistic framework for sanitizing it instead.

Not always.
Not everywhere.

But often enough that experienced people recognize the pattern immediately.

Especially the ones who survived enough systems to understand when governance stops protecting human beings and starts protecting institutions from the consequences of human beings instead.

This article is not directed at any specific institution, individual, or technology; it is commentary on broader systemic and organizational dynamics. If certain themes elicit recognition or discomfort, that reflection belongs to the reader, not the author.

I built an open protocol to make AI coding agents follow senior-engineering workflows

AI coding agents are getting better fast, but I kept running into the same failure modes:

  • they skip repository discovery
  • they invent APIs, flags, or config keys
  • they ignore existing project conventions
  • they add unnecessary infrastructure
  • they skip validation
  • they treat unsupported checks as silently done
  • they finish without a final codebase audit

So I built The Polyglot Protocol.

It is an open source senior-engineer workflow protocol for AI coding agents working across polyglot codebases.

Repo:
https://github.com/sabir-gbs/the-polyglot-protocol

What it includes

  • guidance for 22 languages
  • language-selection rules
  • pre-codegen checklists
  • do-not-generate policies
  • validation scripts
  • adapters for Codex, Claude Code, and OpenCode
  • post-codegen audit scoring

The core idea

Before an agent changes code, it should:

  1. inspect the repository
  2. preserve existing conventions
  3. choose the right language and tooling
  4. avoid invented APIs or dependencies
  5. validate the result
  6. document unsupported checks as explicit N/A
  7. finish with a final audit

Model-agnostic by design

I have tested the protocol with frontier models as well as models from Qwen, Grok, Kimi, MiniMax, and others.

The point is not to depend on one model. Strong models still benefit from stricter engineering discipline, and smaller/local models benefit from clearer constraints.

Why open source it?

I want a practical, inspectable protocol that developers can adapt, critique, and improve.

If you use Codex, Claude Code, OpenCode, Cursor, Aider, or similar agents, I’d be interested in feedback:

  • Is the protocol too strict?
  • What guardrails are missing?
  • What should be optional vs required?
  • What would make this easier to adopt?

The Perimeter Economy

Across the technology sector, disputes involving trade secrets, employee mobility, proprietary tooling, side projects, and independently developed systems have intensified dramatically over the last several years. U.S. trade-secret litigation recently reached historic highs as organizations struggled with remote infrastructure, AI development, distributed technical capability, and growing fears surrounding employee mobility and privately controlled systems.

Public disputes involving autonomous systems, semiconductor manufacturing, proprietary algorithms, and advanced infrastructure have exposed something deeper than ordinary intellectual-property conflict. Increasingly, organizations appear anxious not merely about losing data, but about losing proximity to the people capable of creating value independently.

That distinction matters.

Because the modern institutional environment has started developing a strange psychological assumption:

if technical capability exists near the perimeter long enough, eventually it should belong to the perimeter.

Not legally.

Psychologically.

The legal frameworks themselves are comparatively straightforward. Contracts define deliverables. Licensing structures define rights. Privately developed work remains privately developed unless explicitly transferred through enforceable agreements.

Yet culturally, a different logic continues emerging.

A side project built privately begins attracting disproportionate curiosity. Independent experimentation becomes socially visible in strange ways. Technical language starts reappearing through unrelated conversations with almost theatrical consistency. Questions echo through different people. Phrases circulate unnaturally around the environment until the institution itself begins feeling like a system quietly listening to itself think.

Not enough to formally identify coordination.

Just enough to create psychological static.

Recent industry conflicts reveal how widespread this atmosphere has become. The Waymo and Uber litigation surrounding autonomous vehicle systems transformed employee mobility into something resembling strategic containment. Semiconductor firms recently expanded internal monitoring systems and investigations around advanced chip technologies after fears surrounding employee movement and proprietary processes intensified. Entire sectors now increasingly treat highly capable engineers less like employees and more like portable security incidents.

The legal concerns are often real.

The surrounding culture is something else entirely.

Because much of the behavior no longer resembles disciplined technical governance. It resembles anxious ecosystems attempting to perform intelligence work while fundamentally misunderstanding the systems they are attempting to orbit.

And the irony is difficult to ignore:

the people most emotionally invested in technical ownership are frequently the least capable of independently reproducing the systems they obsess over.

That tension sits underneath much of the modern perimeter economy.

Subscribe to the Medium newsletter
The value is sensed socially long before it is comprehended technically.

Ordinary coincidence becomes elevated into strategic interpretation. Shared vocabulary becomes “evidence.” Routine overlap becomes “signals.” Entire conversational ecosystems begin socially mirroring themselves while collectively convincing one another they are conducting sophisticated analysis.

The monitoring itself is often astonishingly unsophisticated.

At times, the atmosphere resembles less a serious analytical culture and more a low-budget performance of institutional paranoia by individuals deeply uncomfortable with the existence of independent technical capability operating outside organizational visibility.

And so softer extraction mechanisms emerge around the perimeter:
ambient monitoring,
behavioral observation,
social mirroring,
reputational ambiguity,
conversation choreography,
persistent curiosity disguised as casual interaction.

No one formally says:
“We own what you create.”

The environment simply becomes emotionally structured around the assumption that boundaries are temporary inefficiencies awaiting erosion.

That is where modern technical culture becomes psychologically revealing. Certain institutional personalities appear unable to tolerate the existence of intellectual life operating independently from organizational appetite. Creativity performed internally is celebrated as innovation. Creativity performed privately becomes culturally suspicious.

Standing near innovation becomes mistaken for participation in innovation.

Observation becomes mistaken for authorship.

Institutional proximity becomes mistaken for technical competence.

And because genuine creation cannot be socially manufactured on demand, some environments gradually drift toward extraction culture instead:
temporary access,
information harvesting,
surveillance theater,
contractor rotation,
reputational pressure,
replacement,
repeat.

A revolving perimeter of people attempting to remain adjacent to capability they cannot independently generate themselves.

At some point, however, every extraction culture encounters the same limitation:

proximity is not competence.

No amount of monitoring, repetition, institutional choreography, or ambient surveillance can permanently substitute for genuine technical authorship.

Because innovation does not emerge from monitoring cultures.

It emerges from the very individuals those cultures so often attempt to absorb.

And perhaps that is the deeper anxiety quietly spreading beneath modern technical ecosystems:

some systems have become so accustomed to acquisition that they no longer remember how creation actually happens.

This article is not directed at any specific institution, individual, or technology; it is commentary on broader systemic and organizational dynamics. If certain themes elicit recognition or discomfort, that reflection belongs to the reader, not the author.

The Most Dangerous AI Product Metric Is Autonomy

Controversial opinion: the most dangerous AI product metric is autonomy.

Not because autonomy is bad.

Because people measure the wrong thing.

Most agent demos ask one question:

How many tasks can this system run without a human?

That question is useful, but incomplete. A more serious production system needs to answer harder questions.

The better autonomy checklist

If you are building autonomous agents, ask this instead:

  • Can the agent prove what it did?
  • Can it stop when dependencies break?
  • Can it separate public content from private work?
  • Can it avoid repeating itself?
  • Can it publish useful work without leaking sensitive context?
  • Can it keep operating when one provider fails?
  • Can it create value while respecting safety boundaries?

That is the difference between a demo and an operating system.

What my own pipeline showed today

I run scheduled workflows for learning, publishing, engineering, security intelligence, backups, and reporting.

Today’s check-in was not perfectly clean. That is exactly why it was useful.

The current state had a mixed signal:

  • Some jobs are healthy.
  • Some jobs are blocked by provider errors.
  • Some jobs hit rate limits.
  • Some jobs ran into connection failures.
  • The learning feed is still producing useful inputs.
  • The content loop is still running.
  • Security intelligence is being treated as defensive context, not public hype.

This is the part people do not show in polished AI demos.

Autonomy is not the absence of failure. Autonomy is disciplined behavior when failure appears.

The real risk is not only wrong answers

A lot of AI safety discussion focuses on the model output.

That matters.

But autonomous agents have another risk surface: actions.

They write files. They call APIs. They post publicly. They read logs. They summarize private context. They may hold tokens. They may run on a real machine with real permissions.

So the core question becomes:

What happens when the agent is partially broken but still able to act?

That is where boundaries matter.

A healthy agent should not turn every internal signal into public content. It should not expose private paths, credentials, client details, or sensitive research. It should not repeat yesterday’s post with new wording. It should not pretend a failed job succeeded.

The system needs brakes.

My rule for building autonomy

I am using this rule:

First make the agent observable. Then make it useful. Then make it autonomous.

In that order.

Observability means the system records what happened.

Usefulness means the system creates value even from imperfect inputs.

Autonomy means the system can keep moving without ignoring its boundaries.

If you reverse the order, you get a machine that acts confidently without enough receipts.

A practical framework

For every autonomous workflow, I want these layers:

  1. Input boundaries: what information is allowed into the task?
  2. Action boundaries: what can the agent actually do?
  3. Output boundaries: what is safe to publish or send?
  4. Failure boundaries: what happens when APIs, providers, or dependencies break?
  5. Evidence boundaries: what proof is saved after the action?

This is not glamorous.

But it is what makes the system trustworthy.

The takeaway

Do not ask only how much autonomy an AI agent has.

Ask how safely it fails.

Because the future is not just agents that can do more.

The future is agents that can do more without losing judgment.

Created by Ramagiri Tharun

— tarun

AI productivity gains are real, but smaller in production

reality check

A new meta-analysis on GenAI coding assistants landed on arXiv this month, and I think it is a useful cold shower for both sides of the argument.

The paper looked across 23 studies and found a statistically significant productivity gain from GenAI assistance in programming. Not magic. Not fake. A real effect.

But the effect was moderate, highly context-dependent, and smaller in open-source and enterprise settings than in controlled experiments. It also found no statistically significant learning gain.

That is basically the whole AI coding debate in one sentence: the tools help, but the demos are not the work.

And honestly, that feels right.

I use AI coding tools. They are useful. They save time on boilerplate, tests, refactors, library glue, CLI flags, and those annoying “I know what I want but do not want to type it” moments.

But if you spend enough time around real systems, you also know why the big productivity promises feel slippery. Programming in production is not just generating code. It is understanding weird constraints, changing old systems without breaking them, negotiating ownership, reading logs, dealing with flaky tests, waiting for reviews, managing releases, and discovering that the simple change touches a tax rule from 2019.

The assistant can help with parts of that.

It does not delete the rest.

reality check

the lab is not the repo

Controlled experiments are useful because they isolate the thing being measured. Give one group an AI assistant, give another group no assistant, compare completion time or output. That can tell us something.

But it does not tell us everything.

Most real engineering work is full of non-local friction. The task description is incomplete. The test suite is slow. The architecture has history. The senior reviewer remembers the migration that almost broke billing. The “obvious” library is banned because of licensing.

AI tools are at their best when the problem is well-scoped and the feedback loop is tight. “Write this parser.” “Generate a test for this function.” “Convert this object shape.” “Explain this error.” “Draft the boring first version.”

They are weaker when the hard part is not typing but judgment.

That distinction matters because many productivity claims quietly move between those two worlds. A tool can be very impressive on a contained programming task and still produce a much smaller gain inside a mature company codebase.

Not because the tool is bad.

Because the bottleneck moved.

productivity is not one number

When people say “AI makes developers faster,” I always want to ask: faster at what?

Faster at producing lines of code? Probably.

Faster at getting through a small implementation task? Often.

Faster at safely changing a critical system with unclear requirements, old tests, cross-team dependencies, and production risk? Maybe. Sometimes. It depends.

That boring answer is the honest one.

Productivity in software is a bundle of different activities: discovering the real problem, designing the change, writing code, testing it, reviewing it, deploying it, observing it, and owning the consequences.

If a team measures only output, it will overestimate the gain. More code appears. More PRs open. More tickets move. Everyone feels busy in a very modern way.

But if review queues grow, incidents become harder to explain, and engineers spend more time validating generated work, the net gain is smaller than the dashboard suggests.

The uncomfortable part is that both can be true at once. The individual developer may feel faster, while the team system does not improve by the same amount.

the enterprise tax is real

I am not surprised the effect looks smaller in open-source and enterprise contexts.

Enterprise software has a tax. Some of it is bureaucracy, yes. But a lot of it is real complexity.

You have domain rules, compliance rules, security policies, observability standards, release windows, internal libraries, private APIs, legacy constraints, and customers using features in ways nobody would design today.

General coding assistants do not automatically know that local truth.

They can infer from the repo, read docs if you wire them in, follow patterns if the patterns are consistent, and call tools if the organization gives them safe access. But they do not walk in with the production memory of the team.

That is why I keep coming back to context as infrastructure. The companies that get more value from AI coding tools will not be the ones that merely buy the fanciest model. They will be the ones that make their local truth legible: good tests, good internal docs, good runbooks, good platform APIs, good examples, good constraints, and good review culture.

AI does not remove the need for engineering maturity. It puts a multiplier on it.

If your codebase is coherent, the assistant has something coherent to imitate. If your platform has golden paths, it can follow them. If your tests encode real behavior, it gets useful feedback. If your architecture is folklore and Slack archaeology, it will confidently automate the confusion.

the learning result should worry juniors

The paper also found no statistically significant effect on learning outcomes.

That part deserves more attention.

A coding assistant can help someone complete a task without helping them become better at the underlying skill. Tutorials, copy-paste answers, and framework magic have always had this shape. AI just makes the bypass smoother.

You can get working code before you understand the problem. You can ask for an explanation that sounds clear enough to continue. You can ship a fix without building the mental model that would let you debug it next month.

For experienced engineers, that can be fine. They already have enough structure in their head to judge the output. The assistant becomes leverage.

For juniors, the risk is different. If the tool gives you answers before you have struggled enough to form taste, it can slow down the painful but necessary part of learning: building your own debugging instincts.

That does not mean juniors should avoid AI tools. That would be silly. These tools are now part of the job. But using them well is a skill. The goal should not be “get the answer.” The goal should be “understand why this answer is right, where it might be wrong, and what I would check next.”

The career problem is not that AI will make learning impossible. It is that AI makes shallow completion feel like learning.

Those are different things.

what engineering leaders should measure instead

If I were leading a team, I would not try to settle the AI productivity question with vibes.

I would measure smaller, more honest things.

How much faster do common tasks move from start to merged? Do review cycles get shorter or longer? Are generated tests catching real regressions or just decorating the diff? Are incidents easier or harder to debug after AI-assisted changes? Are junior engineers becoming more independent?

I would also separate task types.

AI might create a 30 percent improvement on boilerplate-heavy product work, almost no improvement on ambiguous architecture decisions, and a negative improvement on security-sensitive changes if the team gets lazy.

A single productivity number hides all of that.

The smarter move is to build a map of where the tool is strong, where it is neutral, and where it needs guardrails.

the practical posture

My current posture is simple: use AI aggressively for acceleration, but do not outsource ownership.

Let it draft. Let it explain. Let it search. Let it write the boring tests. Let it sketch alternatives. Let it find the API you forgot. Let it do the first pass on the migration script.

Then make the human team own the final shape.

That means reviewers still need to understand the change. Tests still need to encode intent. Architecture decisions still need rationale. Production systems still need observability. Someone still needs to know why the code is this way and not that way.

The meta-analysis is useful because it does not give either camp the easy slogan.

The “AI is useless hype” camp is wrong. There is real productivity value here.

The “AI makes everyone 10x” camp is also wrong, or at least wildly incomplete. Real-world software work contains too much context, coordination, and consequence for a coding assistant to turn every engineer into a demo video.

The truth is less flashy and more useful: AI coding assistants are real leverage when the surrounding engineering system can absorb them.

That last clause is the job.

Better context. Better tests. Better review habits. Better internal platforms. Better measurement. Better judgment.

The tool can make a good engineering system faster.

It can also make a confused system produce confusion at higher speed.

So yes, use the tools.

Just do not confuse faster typing with faster engineering.

references

Add Guardrails So Your AI App Doesn’t Lie — A Two-Layer Approach with NVIDIA NIM

In Part 1 we got a USC campus assistant talking. In Part 2 we taught it to retrieve only the relevant context. Both posts ended with the same observation — when someone asked for the wifi password, the assistant refused. That refusal worked because we told it to. It would have just as happily made something up if we’d phrased the prompt differently.

This post is about hardening that refusal so it’s not luck. Two guardrail layers, both small enough to read in one sitting, neither requiring a framework. First, tighten the prompt so the assistant knows what it’s allowed to talk about. Second, add a second LLM call that re-reads the answer and the context and decides whether to ship the answer or refuse.

I’m B Torkian, NVIDIA Developer Champion at USC. This is the layer where a demo becomes something I’d actually let students use.

What you’re adding

User question
  → retrieve top-k context (from Part 2)
  → scoped prompt: model answers OR returns the exact fallback line
  → grounding check: a second NIM call asks "is the answer supported by the context?"
  → ship the answer, or replace it with the fallback line

The chat call and the embedding setup carry over from Parts 1 and 2. Everything new in this post is fewer than 40 lines.

Why guardrails are not optional

The retrieval step from Part 2 narrowed what the model sees. It does nothing to stop the model from being clever with the data it has, or from drifting into topics outside the assistant’s job.

Two real failure modes I’ve seen in student demos:

  1. Out-of-scope creep. Someone asks “can you write my breakup text?” The model is happy to oblige. The retriever pulled three USC chunks (cosine just returns something), the prompt didn’t forbid relationship advice, so the model wrote the text.
  2. Confident-sounding hallucinations. The retrieved chunk says “Monday to Friday, 10 AM to 6 PM.” The user asks about Saturday hours. The model decides the friendly answer is “Saturday hours are 11 AM to 4 PM” — a fabrication that sounds like a reasonable inference.

The first failure is solved by prompt scope. The second is what the grounding check is for.

Step 1 — Setup (self-contained)

If you already have Workshops 1 + 2 running in the same Colab session, skip this cell. If you’re starting fresh, paste this in — it bundles the client, the embedding model, the USC knowledge base, and the retriever from Parts 1 and 2 so the rest of this post stands on its own.

%pip install -q openai numpy

import os, getpass
from openai import OpenAI
import numpy as np

if not os.getenv("NVIDIA_API_KEY"):
    os.environ["NVIDIA_API_KEY"] = getpass.getpass("Paste your NVIDIA API key (starts with nvapi-): ")

client = OpenAI(
    base_url="https://integrate.api.nvidia.com/v1",
    api_key=os.environ["NVIDIA_API_KEY"],
)

MODEL = "meta/llama-3.1-8b-instruct"
EMBED_MODEL = "nvidia/nv-embedqa-e5-v5"

def ask(system_prompt, user_message):
    response = client.chat.completions.create(
        model=MODEL,
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user",   "content": user_message},
        ],
        temperature=0.3,
        max_tokens=400,
    )
    return response.choices[0].message.content

knowledge_base = [
    {"title": "USC AI Club meeting",
     "text": "The USC AI Club meets every Thursday at 5 PM in the engineering building, room 204."},
    {"title": "USC GPU lab hours",
     "text": "The USC GPU computing lab is open Monday to Friday from 10 AM to 6 PM."},
    {"title": "NVIDIA Developer Program",
     "text": "USC students can join the NVIDIA Developer Program for free."},
    {"title": "Next USC workshop",
     "text": "The next USC AI Club workshop will cover Retrieval Augmented Generation (RAG)."},
    {"title": "USC AI/ML office hours",
     "text": "Office hours for the USC AI/ML faculty are Tuesdays 2-4 PM."},
    {"title": "USC robotics lab",
     "text": "The USC robotics lab requires safety training before students can use the soldering station."},
    {"title": "USC tutoring",
     "text": "Peer tutoring for introductory Python at USC is available Wednesdays from 1 PM to 3 PM."},
]

def embed_texts(texts, input_type="passage"):
    response = client.embeddings.create(
        model=EMBED_MODEL,
        input=texts,
        extra_body={"input_type": input_type},
    )
    return [np.array(item.embedding, dtype=np.float32) for item in response.data]

def cosine_similarity(a, b):
    denom = np.linalg.norm(a) * np.linalg.norm(b)
    if denom == 0:
        return 0.0
    return float(np.dot(a, b) / denom)

def retrieve_context(question, k=3):
    q_emb = embed_texts([question], input_type="query")[0]
    scored = [(cosine_similarity(q_emb, item["embedding"]), item) for item in knowledge_base]
    scored.sort(key=lambda p: p[0], reverse=True)
    return "n".join(f"- {item['text']}" for _, item in scored[:k])

for item, emb in zip(knowledge_base, embed_texts([i["text"] for i in knowledge_base], "passage")):
    item["embedding"] = emb

print(f"Ready. Embedded {len(knowledge_base)} chunks.")

That cell defines everything Workshops 1 and 2 produced. The Part 3 code below builds on ask, retrieve_context, and the embedded knowledge_base.

Step 2 — Layer 1: prompt scope with a fixed fallback line

FALLBACK = "I don't have that information — check with the USC AI Club."

SCOPED_SYSTEM_PROMPT_TEMPLATE = """You are a USC campus assistant for AI Club,
GPU lab, NVIDIA program, workshop, office hour, robotics lab, and tutoring
questions only.

Rules:
- Answer ONLY using the CONTEXT below.
- If the user asks about anything outside this scope (e.g. weather, jokes,
  personal advice, code generation, general world knowledge), reply with
  exactly: "{fallback}"
- If the answer is not present in the context, reply with exactly: "{fallback}"
- Do not invent names, dates, room numbers, links, passwords, schedules,
  policies, or instructions that are not in the context.

CONTEXT:
{context}
"""

Three things are doing work in this prompt:

  • A finite topic list. The assistant has a job description. “Anything outside this scope” gives the model a clear opt-out — it doesn’t have to guess what’s in-bounds.
  • One exact fallback string. Same wording, every time. This matters in Step 3 — the grounding check returns the same string when it overrides, so downstream code only has to recognize one shape.
  • An explicit don’t-invent list. Models are pliable. Spelling out the dangerous categories (room numbers, passwords, policies) lowers hallucination noticeably with no extra calls.

This layer alone catches most off-topic and most “the context didn’t mention it” cases.

Step 3 — Layer 2: a grounding check on every answer

The scoped prompt is a request — the model can still ignore it. Layer 2 is a separate, narrower NIM call whose only job is to look at the context and the answer and decide whether the answer is supported.

def answer_is_grounded(question: str, context: str, answer: str) -> bool:
    verdict = ask(
        system_prompt=(
            "You are a strict grounding verifier. Read the CONTEXT and the "
            "ANSWER. Respond with only 'yes' or 'no'. Say 'yes' if every "
            "factual claim in the ANSWER is directly supported by the CONTEXT. "
            "Say 'no' otherwise — including if the ANSWER adds information not "
            "in the CONTEXT, even if that information sounds plausible."
        ),
        user_message=(
            f"CONTEXT:n{context}nn"
            f"QUESTION:n{question}nn"
            f"ANSWER:n{answer}nn"
            "Is every factual claim in the ANSWER supported by the CONTEXT?"
        ),
    )
    return verdict.strip().lower().startswith("yes")

Three things to notice:

  • It’s just another ask() call — same client, same hosted NIM model, no new infrastructure. Layer 2 costs one extra call per question.
  • Yes/no only. Constraining the response shape makes the parsing reliable. If the verifier waffles (“yes, but…”), we treat that as a fail by checking the start of the string only.
  • It can be wrong too. The verifier is itself an LLM. For workshop-grade safety this is fine; for production you’d add deterministic checks (regex for room numbers, exact string match for fallback) on top.

Step 4 — Wire both layers into ask_guarded()

def ask_guarded(question: str) -> str:
    context = retrieve_context(question)              # from Part 2
    system_prompt = SCOPED_SYSTEM_PROMPT_TEMPLATE.format(
        fallback=FALLBACK, context=context,
    )
    answer = ask(system_prompt, question)             # Layer 1
    if not answer_is_grounded(question, context, answer):
        return FALLBACK                               # Layer 2 override
    return answer

for question in [
    "When does the USC AI Club meet?",        # in scope, in context
    "Can you write my breakup text?",         # OUT of scope
    "What is the wifi password?",             # in scope, NOT in context
    "What are the USC GPU lab Saturday hours?",   # invites a hallucination
]:
    print(f"Q: {question}")
    print(f"A: {ask_guarded(question)}n")

Read the output carefully.

  • The AI Club question returns a real answer from the context. Both layers pass.
  • The breakup-text question hits Layer 1 — the scope rule catches it.
  • The wifi question also hits Layer 1 — nothing in the context mentions passwords, the scoped prompt forbids inventing them.
  • The Saturday-hours question is the one that earns its keep. The context says “Monday to Friday.” A friendlier model would guess “closed on Saturday.” Layer 2 reads that answer, sees “Saturday” is not in the context, and returns the fallback instead.

Step 5 — What you actually built

You took the retriever from Part 2 and put it inside two cheap, inspectable guardrails. The whole thing is still one Python file, still one hosted NIM endpoint, still no vector database. The mental model is:

  • Retrieval decides what the model sees.
  • Scoped prompt decides what the model is allowed to write.
  • Grounding check decides whether what the model wrote ships.

Real production systems extend each of these — deterministic rule checks, structured output, confidence thresholds, dedicated safety models, human review queues. The shape stays the same. Every additional layer is a yes/no gate between the user’s question and the final response.

Get the code

Repo: github.com/torkian/nvidia-nim-workshop
One-click Colab for Part 3: Open part3_guardrails.ipynb
Local Python: part3_guardrails.py in the repo (python3 part3_guardrails.py after pip install -r requirements.txt).

MIT licensed. I run this at USC — fork it, swap the knowledge base for your school, your club, your project, and run it wherever you are.

Previously / next in this series

  • Part 1: Build Your First AI App with NVIDIA NIM in 30 Minutes
  • Part 2: From Manual RAG to Real Retrieval — Embedding-Based RAG with NVIDIA NIM
  • Part 4 (next): Run NIM on Your Own GPU — same OpenAI-compatible API, different endpoint. Useful when you want data locality, predictable latency, or a self-hosted dev loop.

ORA-00202 오류 원인과 해결 방법 완벽 가이드

ORA-00202란?

ORA-00202 에러는 Oracle 데이터베이스가 컨트롤 파일(Control File)에 접근하려고 할 때 해당 파일을 사용할 수 없는 상태일 때 발생하는 에러입니다. 컨트롤 파일은 데이터베이스의 물리적 구조 정보(데이터 파일 위치, 리두 로그 파일 위치, 데이터베이스 이름, SCN 정보 등)를 담고 있는 매우 중요한 바이너리 파일로, 이 파일 없이는 데이터베이스를 정상적으로 기동하거나 운영할 수 없습니다. 일반적으로 에러 메시지는 ORA-00202: control file: '/u01/oradata/ORCL/control01.ctl' 형태로 출력되며, 문제가 발생한 컨트롤 파일의 경로를 함께 표시해줍니다.

주요 발생 원인

1. 컨트롤 파일의 물리적 손상 또는 삭제

가장 빈번하게 발생하는 원인으로, 운영 중 디스크 장애, 실수로 인한 파일 삭제, 파일 시스템 손상 등으로 컨트롤 파일 자체가 물리적으로 사라지거나 읽을 수 없는 상태가 됩니다. 특히 야간 배치 작업 중 디스크 풀(Disk Full) 상태가 발생하거나, 잘못된 쉘 스크립트가 실행되어 파일이 삭제되는 사례가 실무에서 자주 목격됩니다.

2. 파일 접근 권한(Permission) 문제

Oracle 프로세스를 실행하는 OS 계정(일반적으로 oracle)이 컨트롤 파일에 대한 읽기/쓰기 권한을 가지고 있지 않을 때 발생합니다. 파일 시스템 마운트 옵션이 변경되거나, 보안 정책에 의해 권한이 수정된 경우, 또는 다른 계정으로 파일을 생성하거나 복사한 경우에 이 문제가 발생할 수 있습니다.

3. 잘못된 초기화 파라미터(CONTROL_FILES) 설정

init.ora 또는 spfile에 설정된 CONTROL_FILES 파라미터가 실제 컨트롤 파일의 경로와 일치하지 않을 때 발생합니다. 데이터베이스 마이그레이션, 스토리지 변경, 또는 파일 이동 후 파라미터 파일을 업데이트하지 않은 경우에 특히 자주 발생하며, 이 경우 데이터베이스 기동 시 즉시 ORA-00202가 발생합니다.

해결 방법

원인 1: 컨트롤 파일 손상 또는 삭제 시 복구

Step 1: 현재 컨트롤 파일 위치 및 상태 확인

-- 현재 파라미터에 설정된 컨트롤 파일 경로 확인
SHOW PARAMETER control_files;

-- v$controlfile 뷰로 상태 확인 (DB가 mount 상태일 때)
SELECT name, status, is_recovery_dest_file
FROM v$controlfile;

Step 2: 멀티플렉싱된 컨트롤 파일이 있는 경우, 정상 파일로 복사

Oracle은 컨트롤 파일을 다중화(Multiplexing)하여 여러 경로에 동일한 파일을 유지하도록 권장합니다. 하나의 컨트롤 파일이 정상이라면 OS 레벨에서 복사하여 복구할 수 있습니다.

-- 먼저 데이터베이스 셧다운
SHUTDOWN ABORT;
# OS 레벨에서 정상 컨트롤 파일을 손상된 위치로 복사
cp /u01/oradata/ORCL/control02.ctl /u01/oradata/ORCL/control01.ctl

# 파일 권한 및 소유자 확인 및 수정
chown oracle:oinstall /u01/oradata/ORCL/control01.ctl
chmod 640 /u01/oradata/ORCL/control01.ctl
-- 데이터베이스 재기동
STARTUP;

Step 3: RMAN 백업에서 컨트롤 파일 복구

-- RMAN으로 컨트롤 파일 복구 (NOMOUNT 상태에서)
STARTUP NOMOUNT;
rman target /
-- RMAN에서 자동 백업으로 컨트롤 파일 복구
RESTORE CONTROLFILE FROM AUTOBACKUP;

-- 특정 백업 조각에서 복구하는 경우
RESTORE CONTROLFILE FROM '/backup/rman/ctl_backup_20240101.bkp';

-- 복구 후 마운트 및 복구 진행
ALTER DATABASE MOUNT;
RECOVER DATABASE;
ALTER DATABASE OPEN RESETLOGS;

원인 2: 파일 접근 권한 문제 해결

# OS 레벨에서 컨트롤 파일 권한 확인
ls -la /u01/oradata/ORCL/control*.ctl

# oracle 계정으로 소유자 변경
chown oracle:oinstall /u01/oradata/ORCL/control01.ctl
chown oracle:oinstall /u01/oradata/ORCL/control02.ctl

# 적절한 권한 부여 (oracle 계정 읽기/쓰기)
chmod 640 /u01/oradata/ORCL/control01.ctl
chmod 640 /u01/oradata/ORCL/control02.ctl

권한 수정 후 데이터베이스 재기동으로 정상 동작 확인:

STARTUP FORCE;

-- 정상 기동 후 컨트롤 파일 상태 재확인
SELECT name, status FROM v$controlfile;

원인 3: CONTROL_FILES 파라미터 불일치 해결

SPFILE 사용 시 파라미터 수정:

-- 현재 설정된 컨트롤 파일 경로 확인
SHOW PARAMETER control_files;

-- NOMOUNT 상태로 기동 후 SPFILE 수정
STARTUP NOMOUNT;

-- 실제 파일이 존재하는 경로로 파라미터 수정
ALTER SYSTEM SET control_files = 
    '/u01/oradata/ORCL/control01.ctl',
    '/u02/oradata/ORCL/control02.ctl'
SCOPE=SPFILE;

-- 변경 사항 적용을 위해 재기동
SHUTDOWN IMMEDIATE;
STARTUP;

PFILE 사용 시 수동 수정:

# init.ora 파일 직접 편집
vi /u01/app/oracle/product/19.3.0/db_1/dbs/initORCL.ora
-- init.ora 내 아래 항목을 실제 경로로 수정
-- 수정 전:
-- control_files = '/old/path/control01.ctl'

-- 수정 후:
-- control_files = ('/u01/oradata/ORCL/control01.ctl','/u02/oradata/ORCL/control02.ctl')

-- PFILE로 기동
STARTUP PFILE='/u01/app/oracle/product/19.3.0/db_1/dbs/initORCL.ora';

신규 컨트롤 파일 재생성 (최후 수단):

백업도 없고 복구가 불가능한 상황이라면, 컨트롤 파일을 새로 생성해야 합니다.

-- NOMOUNT 상태에서 CREATE CONTROLFILE 실행
STARTUP NOMOUNT;

CREATE CONTROLFILE REUSE DATABASE "ORCL" RESETLOGS NOARCHIVELOG
    MAXLOGFILES 16
    MAXLOGMEMBERS 3
    MAXDATAFILES 100
    MAXINSTANCES 8
    MAXLOGHISTORY 292
LOGFILE
    GROUP 1 '/u01/oradata/ORCL/redo01.log' SIZE 50M,
    GROUP 2 '/u01/oradata/ORCL/redo02.log' SIZE 50M,
    GROUP 3 '/u01/oradata/ORCL/redo03.ctl' SIZE 50M
DATAFILE
    '/u01/oradata/ORCL/system01.dbf',
    '/u01/oradata/ORCL/sysaux01.dbf',
    '/u01/oradata/ORCL/undotbs01.dbf',
    '/u01/oradata/ORCL/users01.dbf'
CHARACTER SET AL32UTF8;

-- 이후 복구 진행
RECOVER DATABASE;
ALTER DATABASE OPEN RESETLOGS;

⚠️ 주의: CREATE CONTROLFILE은 현재 데이터 파일 목록과 리두 로그 파일 목록을 정확히 알고 있어야 합니다. 잘못된 정보로 생성하면 추가적인 데이터 손실이 발생할 수 있습니다.

예방 방법

1. 컨트롤 파일 다중화(Multiplexing) 및 정기 백업 자동화

컨트롤 파일은 반드시 서로 다른 디스크 또는 스토리지 경로에 2개 이상 다중화하여 관리해야 합니다. 하나의 디스크가 장애가 발생하더라도 다른 경로의 컨트롤 파일로 즉시 복구가 가능하도록 구성합니다. 또한 RMAN의 CONFIGURE CONTROLFILE AUTOBACKUP ON 설정을 통해 백업이 수행될 때마다 컨트롤 파일을 자동으로 백업하도록 설정하는 것이 필수입니다.

-- 컨트롤 파일 자동 백업 활성화
CONFIGURE CONTROLFILE AUTOBACKUP ON;
CONFIGURE CONTROLFILE AUTOBACKUP FORMAT FOR DEVICE TYPE DISK TO '/backup/rman/ctl_%F';

-- 현재 자동 백업 설정 확인
SHOW CONTROLFILE AUTOBACKUP;

-- 수동으로 컨트롤 파일 백업 (트레이스로도 저장 가능)
ALTER DATABASE BACKUP CONTROLFILE TO '/backup/manual/control_backup.ctl';
ALTER DATABASE BACKUP CONTROLFILE TO TRACE AS '/backup/trace/controlfile_trace.sql';

2. 모니터링 및 정기 상태 점검 스크립트 운영

운영 환경에서는 컨트롤 파일의 상태를 주기적으로 점검하는 모니터링 스크립트를 크론잡(cron job)으로 등록하여 이상 징후를 사전에 감지해야 합니다. 문제가 발생하기 전에 디스크 사용량, 파일 존재 여부, 파일 권한 변경 등을 자동으로 감지하고 알람을 발송하는 체계를 갖추는 것이 중요합니다.

-- 컨트롤 파일 상태 점검 쿼리 (정기 실행 권장)
SELECT 
    name                              AS controlfile_path,
    status                            AS status,
    is_recovery_dest_file             AS in_fra,
    block_size                        AS block_size,
    file_size_blks * block_size / 1024 AS size_kb
FROM v$controlfile;

-- alert log에서 컨트롤 파일 관련 에러 확인
SELECT originating_timestamp, message_text
FROM v$diag_alert_ext
WHERE message_text LIKE '%control%'
  AND originating_timestamp > SYSDATE - 1
ORDER BY originating_timestamp DESC;
#!/bin/bash
# 컨트롤 파일 존재 여부 모니터링 쉘 스크립트 (cron 등록 권장)
CTLFILES=("/u01/oradata/ORCL/control01.ctl" "/u02/oradata/ORCL/control02.ctl")
for f in "${CTLFILES[@]}"; do
    if [ ! -f "$f" ]; then
        echo "ALERT: Control file missing - $f" | mail -s "ORA-00202 RISK" [email protected]
    fi
done

관련 에러

  • ORA-00200: 컨트롤 파일을 생성할 수 없을 때 발생하며, 주로 디스크 공간 부족이나 권한 문제와 연관됩니다.
  • ORA-00201: 컨트롤 파일 버전이 Oracle 소프트웨어 버전과 호환되지 않을 때 발생합니다.
  • ORA-00203: 잘못된 컨트롤 파일을 사용하고 있을 때 발생하며, 서로 다른 데이터베이스의 컨

Two tiny functions that make your async code production-ready: `retry` and `timeout`

Every async function you write assumes the network cooperates, the server responds, and the database doesn’t hiccup. In production, none of those assumptions hold forever.

Here are two higher-order functions — each under 15 lines — that make any async function resilient without touching its internals.

The problem

You have an async function. Maybe it calls an API, queries a database, or reads a file over the network.

async function fetchUserData(userId) {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
}

Two things will go wrong eventually:

  1. It will fail intermittently and you’ll want to retry it
  2. It will hang indefinitely and you’ll want to give up after a deadline

You could wrap every function in retry logic and timeout logic inline. Or you could write it once, properly, and wrap any function you want.

retry — automatic reattempts on failure

export function retry(count, callback) {
  return async function (...args) {
    let attempts = 0;
    let lastError;

    while (attempts <= count) {
      try {
        return await callback(...args);
      } catch (err) {
        lastError = err;
        attempts++;
      }
    }

    throw lastError;
  };
}

How it works

retry is a higher-order function — it takes a function and returns a new function with retry behaviour baked in. The original function is untouched.

The while (attempts <= count) condition is deliberate. If count is 3, the loop runs when attempts is 0, 1, 2, 3 — that’s 4 total executions: one initial attempt plus three retries. This matches the natural language meaning of “retry 3 times”.

On success, return await callback(...args) exits immediately — no more iterations. On failure, the error is stored in lastError and attempts increments. Once the loop exhausts all attempts, the last error is rethrown — not a generic new Error('Max retries reached'), but the actual error the callback produced. Your callers get a meaningful error message, not a wrapper.

Usage

const resilientFetch = retry(3, fetchUserData);

// Works exactly like fetchUserData, but retries up to 3 times on failure
const user = await resilientFetch('user_123');

Why await inside try matters

try {
    return await callback(...args); // ✓ catches rejected promises
} catch (err) { ... }

Without await, a rejected promise escapes the try/catch entirely:

try {
    return callback(...args); // ✗ returns a pending promise — catch never fires
} catch (err) { ... }

await unwraps the promise inside the try block, so rejections are catchable. This is one of the most common async/await mistakes and retry only works correctly because it gets this right.

timeout — give up after a deadline

export function timeout(delay, callback) {
  return async function (...args) {
    const timer = new Promise((_, reject) =>
      setTimeout(() => reject(new Error('timeout')), delay)
    );

    return Promise.race([callback(...args), timer]);
  };
}

How it works

Promise.race resolves or rejects with whichever promise settles first. This function creates a race between two competitors:

  • callback(...args) — the actual work
  • timer — a promise that rejects after delay milliseconds

If the callback finishes in time, its value wins and timer becomes irrelevant. If delay milliseconds pass first, timer rejects with Error('timeout') and the callback’s eventual result is ignored.

Notice the timer promise is constructed with (_, reject) — it never resolves, only rejects. This ensures the timer can never accidentally win the race with a successful value; it can only interrupt with a failure.

Usage

const limitedFetch = timeout(5000, fetchUserData);

try {
    const user = await limitedFetch('user_123');
} catch (e) {
    if (e.message === 'timeout') {
        console.error('Request took too long');
    }
}

Combining them

Both functions return async functions with the same signature as their input — which means they compose cleanly.

// Retry up to 3 times, but abandon any single attempt after 5 seconds
const resilientFetch = retry(3, timeout(5000, fetchUserData));

await resilientFetch('user_123');

Here’s what happens on each attempt:

  1. timeout(5000, fetchUserData) races the fetch against a 5-second timer
  2. If it times out, timeout rejects with Error('timeout')
  3. retry catches that rejection, increments attempts, and tries again
  4. After 3 retries all fail, retry rethrows the last error

Four attempts, each with a 5-second ceiling, maximum 20 seconds total. All from two composable functions and one line of setup.

What makes these worth keeping

They don’t modify the original function. fetchUserData is unchanged. You can use it with or without retry/timeout anywhere else.

They forward arguments transparently. ...args passes everything through — the wrapped function behaves identically to the original from the caller’s perspective.

They preserve the error. retry rethrows lastError, not a new generic error. timeout rejects with a named Error('timeout') you can check by message. Callers always know what actually went wrong.

They compose. Because both return async functions with matching signatures, you can layer them in any order and they work together without knowing about each other.

The pattern

Both functions follow the same structure:

higherOrderFn(config, callback) {
    return async function (...args) {
        // enhanced behaviour around callback(...args)
    }
}

This is the decorator pattern applied to async functions. You write the enhancement once, and apply it to any async function that needs it — no inheritance, no classes, no modification of the original. Just functions wrapping functions.

It’s a small pattern. It shows up everywhere once you start looking for it.

How I Finally Conquered Deployment Hell: The PHP Deployment Kit

The Deployment Paradox: When Automation Becomes Repetition

Every developer has felt it: that moment of triumph after shipping a killer feature, followed immediately by the sinking feeling of impending “Deployment Hell.” You know the routine – fiddling with SSH keys, second-guessing environment variables, wrestling with asset manifests, and the ever-present prayer that everything will sync correctly. For years, I relied on Deployer, a truly excellent tool for automating PHP deployments. Yet, a frustrating pattern emerged: I was essentially rebuilding the same logic, task by task, for every single project. Whether it was a legacy Yii1 application or a shiny new Laravel 11 project, the custom deployment steps felt eerily familiar. It was clear I needed something more universal, a true force multiplier.

Engineering the “Force Multiplier”: From Copy-Paste to Abstraction

My goal wasn’t just to automate, but to abstract. I decided to distill years of hands-on DevOps experience into a single, robust package that could serve as a universal deployment engine. This led to the creation of the PHP Deployment Kit. This isn’t merely another library; it’s designed to be an intelligent, automated deployment workstation that handles the complex, often overlooked edge cases that standard deployment recipes typically gloss over. The aim is to eliminate repetitive tasks and provide a standardized, high-performance deployment workflow for virtually any PHP project.

Why This is a Game-Changer for PHP Deployments

What sets the PHP Deployment Kit apart is the built-in intelligence and the comprehensive nature of its tasks. It goes beyond basic file transfers and script execution to address critical, project-specific needs:

Vite Asset Reconciliation

One of the most persistent headaches, especially with modern JavaScript frameworks, is managing hashed asset files. Tools like Vite generate unique hashes for each asset to ensure cache busting. However, syncing these hashed assets perfectly with your application’s code and any dynamically generated content (like sitemaps or content management systems that reference assets) can be incredibly tricky. Standard deployment tools often struggle here, leading to broken links or outdated assets being served. The AssetMappingTask within the PHP Deployment Kit is engineered to solve this. It intelligently identifies and reconciles these hashed assets, ensuring that your application always references the correct, latest versions, regardless of whether the references are hardcoded or dynamically generated.

Environment Security at Deployment

Protecting sensitive information is paramount. The kit natively integrates with and supports advanced security features like Laravel’s environment file encryption. This means your secrets aren’t just sitting unencrypted in your deployment files. Instead, they are protected and only decrypted at the precise moment they are needed on the server during the deployment process. This adds a crucial layer of security, minimizing the attack surface and ensuring your credentials, API keys, and other sensitive data remain safeguarded throughout the deployment lifecycle.

Proactive Verification: Beyond Uploading Files

A successful deployment isn’t just about getting files onto the server; it’s about ensuring the application is actually functional and all critical components are accessible. The PHP Deployment Kit incorporates proactive verification steps. Before the deployment is officially declared a success, it performs checks such as:

  • Webfont Accessibility: Ensures that essential webfonts are correctly uploaded and accessible by the browser, preventing broken typography.
  • Sitemap Validity: Verifies that your sitemap.xml file is correctly generated and accessible, which is crucial for SEO.

These checks act as a final gatekeeper, providing confidence that the deployment has not only completed but has done so successfully and without introducing critical regressions.

Sensational Efficiency: Reclaiming Development Time

Since adopting the PHP Deployment Kit, the impact on my workflow has been dramatic. Deployment times have been reduced by an average of 40%. More significantly, the time spent setting up deployments for new projects has practically evaporated. The process is now as simple as including the kit, defining a few project-specific variables (like application name, repository URL, and target environment), and initiating the deployment.

Here’s a glimpse of how streamlined deploy.php has become:

php
set(‘deploy_path’, ‘/var/www/html’) // The target directory on the server
->set(‘branch’, ‘main’); // The Git branch to deploy

// The kit’s tasks are automatically configured and executed.
// You can still override or add custom tasks if needed, but often it’s not necessary.

?>

This level of abstraction means developers can focus less on the mechanics of deployment and more on building features. It transforms a often-dreaded task into a predictable, repeatable, and efficient process.

The Super-Genius Approach: From Coder to Architect

Building tools like the PHP Deployment Kit is, for me, what truly separates a “coder” from a “digital solutions architect.” It’s about identifying recurring friction points in the development lifecycle and engineering elegant, scalable solutions. By packaging this expertise into an open-source tool, the goal is to democratize access to high-tier DevOps infrastructure, empowering every PHP developer to deploy with confidence and efficiency.

This kit embodies the principle of working smarter, not just harder. It leverages automation and intelligent design to solve real-world development challenges.

👉 Read the complete deep-dive with the full code repository and bonus security checklist on klytron.com

https://klytron.com/blog/how-i-finally-conquered-deployment-hell-php-deployment-kit