Skip to content
Back to skills

Rebind Html

ASecurity

Pipeline stage 3 (the HTML surgical output engine) for report-regeneration. Applies a Binding Manifest to a COPY of an HTML report template and emits the regenerated HTML: frozen nodes stay byte-identical, surgical/regenerate nodes are rebuilt under the zero-literal construction rule (strip the old value, THEN write the new one), needs-review nodes are left untouched but visibly flagged + logged. Stdlib-only (re/html.parser/json), jinja2 optional acceleration, runs on Python 3.9.6. NOT for st...

  • 7 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 23, 2026
ai-agentspythongobashnodeexpress

Works with

  • cli

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned September 23, 2026

npx -y skills add mcorbett51090/RavenClaude --skill rebind-html --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Rebind Html?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Rebind Html
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/mcorbett51090-rebind-html/badge)](https://www.skillsdirectory.com/skills/mcorbett51090-rebind-html)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: rebind-html
description: "Pipeline stage 3 (the HTML surgical output engine) for report-regeneration. Applies a Binding Manifest to a COPY of an HTML report template and emits the regenerated HTML: frozen nodes stay byte-identical, surgical/regenerate nodes are rebuilt under the zero-literal construction rule (strip the old value, THEN write the new one), needs-review nodes are left untouched but visibly flagged + logged. Stdlib-only (re/html.parser/json), jinja2 optional acceleration, runs on Python 3.9.6. NOT for structure inference (that's an earlier pipeline stage) or the fidelity harness (a separate downstream track) or Office/docx output (a later release lane)."
---

# Skill: rebind-html

## What this is

The **HTML surgical output engine** for `report-regeneration` — pipeline stage 3. Given
a template HTML file, a Binding Manifest
([`../../knowledge/binding-manifest.schema.json`](../../knowledge/binding-manifest.schema.json)),
and resolved new-source data, it produces a **same-format review-ready draft** by
performing schema-validated surgery on a **copy** of the template — never by
re-rendering from an abstract model. This is the "surgeon, not a renderer" model from
[`../../knowledge/core-architecture-spec.md`](../../knowledge/core-architecture-spec.md)
§1, applied to the HTML/web→PDF output format.

The engine consumes a manifest; it does **not** infer structure or propose bindings
(that is an earlier pipeline stage) and it does **not** run the fidelity harness (V1-V6 +
period-coherence — a separate, downstream track). Its one job is: **apply exactly what
the manifest says, per node class, and prove the frozen ones didn't move.**

## The four node classes (§4 of the architecture spec)

| Class | What this engine does |
|---|---|
| `frozen` | **No mutation.** After every other binding is applied, the engine re-checks that the node's exact byte span is identical between template and output (`_assert_frozen_unchanged`) — a defensive proof, not an assumption. |
| `surgical` | **Strip, then write** — two separate mutations, in that order. `strip_inner_by_id` (or `strip_attr_by_id` for a void element) empties the old value; `write_inner_by_id`/`write_attr_by_id` writes the new one. At the instant between the two calls the node provably carries no old instance value — by construction, not a downstream check. |
| `regenerate` | Same strip-then-write invariant, but the new value is **rendered** from a template string against the new-data JSON (stdlib `string.Template`, dotted-placeholder subclass — jinja2 is optional acceleration for templates that use native `{{ }}`/`{% %}` control syntax). A raster/void node (an `<img>`) is rebound by **attribute** (`src`, + `alt` if provided) — never by transplanting the old binary, per the spec's construction rule: "a transplanted binary blob cannot be proven data-free." |
| `needs-review` | **Left completely untouched** — content and attributes unchanged — but flagged: a machine-readable `data-rebind-flag="needs-review"` (+confidence) attribute on the opening tag, plus a visible on-page badge (`⚠ NEEDS REVIEW — human sign-off required (...)`) inserted as the node's first child (or as a sibling for a void element). Logged in the printed change-manifest. Never ships silently — this is guarantee #2 made mechanical. |

## Usage

```bash
python3 plugins/report-regeneration/skills/rebind-html/rebind_html.py \
    --template  tests/fixtures/report-regeneration/sample-report.html \
    --manifest  path/to/binding-manifest.json \
    --new-data  path/to/new-data.json \
    --out       tests/fixtures/report-regeneration/_out/regenerated.html
```

Add `--pretty` to pretty-print the printed JSON change-manifest. Every invocation prints
a JSON object to stdout: `{"schema": "report-regeneration/rebind-html@1", "ok": true|false,
..., "changes": [...]}`. Exit code `0` = success; `2` = usage / path-guard /
manifest-schema / anchor-not-found / missing-data-query / missing-new-data-key error
(never a silent partial write — a failure aborts the whole run and touches `--out` not at
all).

`--template`/`--manifest`/`--new-data`/`--out` are **relative paths, path-guarded**
(mirrors [`../../scripts/seed_defects.py`](../../scripts/seed_defects.py) and
`ravenclaude-core`'s `skills/svg-report-lint/lint.py` `_repo_root()`/`_safe_path()`
convention): no absolute paths, no `..` traversal, must resolve inside the repo root.
`--out` may never equal `--template` — this script works on a **copy**; the template file
on disk is never mutated (verified in `tests/test_rebind.py`'s CLI suite via a
before/after SHA-256 hash of the template file).

## The new-data lookup + template contract

A binding's `data_query.expression` is a **dot-path** into the `--new-data` JSON (e.g.
`"revenue.total"` looks up `{"revenue": {"total": ...}}`). `surgical` writes the resolved
value directly. `regenerate` treats the resolved value as either:

- a **plain string** — a template, rendered with `${dotted.path}` placeholders resolved
  against the SAME new-data object (stdlib-only, always works); or a native Jinja
  template (`{{ }}`/`{% %}`) — rendered via `jinja2` if it's importable, else a loud
  `RebindError` (never a silent stdlib mis-render of control-flow syntax that stdlib
  templating cannot express); or
- an **object** — for a raster/void node, `{"src": "...", "alt": "..."}` (alt optional);
  for a text node with an explicit template key, `{"template": "..."}`.

A missing dot-path segment, an unresolvable anchor, or a class/data_query mismatch
against the schema's `allOf` rule (frozen carries no `data_query`; every other class
must) is a loud `RebindError` → exit 2 — never a guess.

## Anchor support (this is an HTML-only engine)

Only `anchor.kind == "element_id"` and a simple `anchor.kind == "css_selector"` of the
shape `"#element-id"` are resolvable (both reduce to the same `id`-attribute lookup, via
regex — deliberately not a full DOM rebuild, matching `seed_defects.py`'s convention).
`json_pointer` and `ooxml_path` anchors are Office/docx-format anchors from the same
manifest schema; this engine rejects them loudly (`RebindError`) rather than guessing —
Office output is a later release lane (v0.2.0 per the plugin's CLAUDE.md).

## Tests

[`tests/test_rebind.py`](tests/test_rebind.py) — stdlib `unittest`, no pytest required:

- **`TestCoreMechanics`** — a small synthetic fixture exercising all four classes: frozen
  byte-identity (+ a must-fail check that a genuinely mutated frozen node IS caught),
  surgical strip-then-write with the old value asserted absent at both the strip midpoint
  and the final node, regenerate text + regenerate raster (both asserting the old
  value/asset is gone), needs-review flagging + untouched content, `html.parser`
  parseability, the stdlib `${dotted.path}` template renderer, the jinja2-absent
  loud-failure path, and three manifest-schema-violation rejections.
- **`TestSampleReportCorpus`** — end-to-end against the real
  [`tests/fixtures/report-regeneration/sample-report.html`](../../../../tests/fixtures/report-regeneration/sample-report.html)
  corpus fixture: a hand-built manifest covering a frozen void element (`#logo-header`),
  two surgical text nodes (`#kpi-revenue`, `#hdr-period`), a regenerate narrative
  (`#exec-summary-narrative`), a regenerate raster (`#chart-region-mix`), and the
  fixture's own deliberately-tricky needs-review case (`#tagline-text`, the
  percent-shaped marketing literal called out in the corpus README) — asserting new
  values present / old values absent at each node, the template file untouched on disk,
  and the whole output still parses.
- **`TestCLI`** — process-boundary tests: happy path (exit 0, output written, template
  SHA-256 unchanged), `--out == --template` rejection, path-traversal rejection, and
  absolute-path rejection (all exit 2).

Run: `python3 plugins/report-regeneration/skills/rebind-html/tests/test_rebind.py -v`

## Purity contract

- **Stdlib-only** — `argparse`, `html.parser`, `json`, `re`, `string`, `pathlib`, `sys`.
  No pip installs required. `jinja2` is imported via a graceful `try/except` and is
  **optional acceleration only** — every `${dotted.path}`-only template (which is what
  this skill's whole test suite exercises) works with jinja2 absent.
- **Runs unmodified on Python 3.9.6** — `from __future__ import annotations`; no PEP 604
  union syntax (`X | Y`), no `match` statement.
- **No network, no subprocess** inside the engine itself.
- **Path-guarded** — see Usage above.
- **Exit-coded** — deterministic `0`/`2`; no partial write on failure.
- **Zero-literal construction, by construction** — every surgical/regenerate mutation is
  a strip call followed by a write call; there is no code path that writes a new value
  without first stripping the old one at that anchor.

Files in this skill

  • SKILL.md8.7 KB
  • emit_pdf.py14.5 KB
  • rebind_html.py37 KB
  • tests/test_rebind.py20 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…