diff --git a/ADDON-SKELETON-SPEC.md b/ADDON-SKELETON-SPEC.md index 470292a..406e474 100644 --- a/ADDON-SKELETON-SPEC.md +++ b/ADDON-SKELETON-SPEC.md @@ -1,14 +1,14 @@ # Addon Skeleton Specification **Project:** kane-diagnostics -**Version:** 2.0 -**Prerequisite reading:** README.md, DSC-development-map.md, HANDOFF.md, CODING-GUIDELINES.md, INSTITUTIONAL-RELATIONSHIPS.md, FOR-PARTICIPANTS-AND-PROFESSIONALS.md +**Version:** 3.0 +**Prerequisite reading:** README.md, DSC-development-map.md, CODING-GUIDELINES.md, INSTITUTIONAL-RELATIONSHIPS.md, FOR-PARTICIPANTS-AND-PROFESSIONALS.md, ASSOCIATION-CHANNEL-MODEL.md --- ## The Standard -This document defines the skeleton standard for all Kane Diagnostics Hubzilla addons — present and future. The three addons described below are the first implementations of the standard, not the definition of it. Any addon added to this project follows the same skeleton: same file layout, same Widget structure, same PDL pattern, same config template fields, same spool contract discipline, same coding constraints. +This document defines the skeleton standard for all Kane Diagnostics Hubzilla addons — present and future. The three addons described below are the first implementations of the standard, not the definition of it. Any addon added to this project follows the same standard: same file layout, same Widget structure, same PDL pattern, same config template fields, same spool contract discipline, same coding constraints. The standard changes only by deliberate revision of this document. A revised document increments the version number and is committed to the repo. The previous version is never deleted — it remains as the historical record of what was built under it. @@ -16,6 +16,11 @@ If you are reading this document and the repo contains addon code, read the code Nothing in this document should be built without the operator's input and confirmation at each step. +**Version history:** +- v1.0 — initial skeleton standard, single `corpus_builder_group_id` config, single `{addon}.php` file +- v2.0 — Widget directory structure, four-tab institutional directory, `listings.json.template` with Core slots +- v3.0 — per-association config structure, split-file PHP pattern (`{addon}.php` + `{addon}_renderer.php` + `{addon}_spool.php`), `xchan` field in listings template + --- ## The Three Addons @@ -24,15 +29,14 @@ Nothing in this document should be built without the operator's input and confir **Purpose:** Present the ten structural preconditions of an HOA association. Public-facing — readable without authentication. This is the first thing a participant reads and the first thing an attorney reads. -**Route:** `/vs01` +**Route:** `/vs01/[association-slug]/[VS-code]` -**Access:** Public read. No authentication required to view. HOA_MEMBER standing required to submit a Vital Signs record for a specific association. +**Access:** Public read. HOA_MEMBER standing (SASE Participant or Corpus Builder group) required to submit a VS record for a specific association. **What it does:** -- Presents each of the ten Vital Signs in plain language -- Explains why each one matters to the homeowner -- Provides public record lookup guidance for each Vital Sign (Recorder of Deeds, Secretary of State, IDFPR, etc.) -- Allows a verified participant to submit a Vital Signs record for their association +- Presents each of the ten Vital Signs in plain language, one at a time per JSON schema +- Provides verification source guidance for each Vital Sign +- Allows a verified participant to submit a VS record for their association - Stores the record via the orchestrator spool **What it does not do:** @@ -77,25 +81,27 @@ Nothing in this document should be built without the operator's input and confir - Presents the scenario carousel — browse only, no selection required - Accepts the participant's account in their own words - Writes the submission to the orchestrator spool as a TMP record -- Provides an operator-only `/scn01/manage` interface for scenario management and TMP review +- Provides an operator-only `/scn01/[association-slug]/manage` interface for TMP review **What it does not do:** - It does not present Vital Signs - It does not present case law - It does not manage the institutional directory -**Relationship to pilot:** +**Relationship to pilot:** The pilot `ds01` addon in `caselaw-document-access` is the functional prototype for this addon. The new `scn01` is a clean rebuild on the complete foundation — not a port of the pilot code. The pilot is reference, not source. --- ## Skeleton File Structure -Each addon skeleton follows this structure. Files marked `[placeholder]` contain the correct structure but no functional logic. +Each addon skeleton follows this structure. The `{addon}.php` entry point, `{addon}_renderer.php`, and `{addon}_spool.php` are the three mandatory PHP files for any addon that renders JSON-driven forms and POSTs to the orchestrator. ``` hubzilla/addon/{addon}/ - {addon}.php — main addon file: hooks, load/unload, content routing + {addon}.php — entry point: hooks, routing, access state, config/listings loaders, CSRF + {addon}_renderer.php — form renderer: schema loader, all field type functions, flags, sources + {addon}_spool.php — POST handler: validation, envelope builder, orchestrator POST {addon}.apd — app descriptor mod_{addon}.pdl — PDL layout: aside, content, right_aside config.json.template — all config fields documented, no secrets @@ -109,30 +115,59 @@ hubzilla/addon/{addon}/ {addon}.js — all behavior, no inline scripts anywhere contracts/ spool-v1.json — the spool envelope contract this addon produces + vital-signs/ — vs01 only: ten VS JSON schema files + VS-RENDERER-SPEC.md — vs01 only: renderer contract document README.md — one paragraph: what this addon does and does not do ``` +**File size constraint:** No PHP file may exceed 500 lines. If the entry point approaches that limit, extract further — for example, a `{addon}_manage.php` for the operator TMP interface. The split point is always a single `require_once` in the entry point. + --- ## Skeleton Content Requirements -### {addon}.php +### {addon}.php — Entry Point Must contain, in order: 1. **File header** — addon name, description, version, min/max Hubzilla version -2. **Hook registration** — `{addon}_load()` and `{addon}_unload()` with PDL and Widget hooks -3. **PDL loader** — `{addon}_load_pdl()` following the established convention -4. **Helper** — `{addon}_h()` — the HTML escape function -5. **Access state** — `{addon}_access_state()` — returns `'public'`, `'participant'`, or `'operator'` -6. **Access wall** — `{addon}_access_wall()` — plain language, SASE link -7. **Content router** — `{addon}_content()` — loads CSS/JS, checks access, routes by path and method -8. **Config loader** — `{addon}_load_config()` -9. **Listings loader** — `{addon}_load_listings()` — reads `listings.json`, returns structured array; fails visibly if file is missing or malformed -10. **CSRF token and verify** — standard pattern -11. **Placeholder content functions** — one per route, each returning a `// TODO` string with a plain description of what goes here +2. **require_once** — loads `{addon}_renderer.php` and `{addon}_spool.php` +3. **Hook registration** — `{addon}_load()` and `{addon}_unload()` with PDL and Widget hooks +4. **PDL loader** — `{addon}_load_pdl()` following the established convention +5. **Helper** — `{addon}_h()` — the HTML escape function +6. **Access state** — `{addon}_access_state($association_slug)` — returns `'public'`, `'participant'`, `'professional'`, or `'operator'` +7. **Professional check** — `{addon}_is_professional()` — reads `listings.json` xchan field +8. **Access wall** — `{addon}_access_wall($association_slug)` — plain language, SASE link +9. **Content router** — `{addon}_content()` — loads CSS/JS, reads argv(1) for association slug, argv(2) for sub-route, routes by path and method +10. **Render functions** — one per route: index, landing, form, manage (TMP review) +11. **Config loader** — `{addon}_load_config()` +12. **Listings loader** — `{addon}_load_listings()` +13. **CSRF token and verify** — standard pattern -No functional logic in the skeleton. No database calls. No file I/O beyond config and listings loaders. No orchestrator calls. Those come after the skeleton is confirmed. +### {addon}_renderer.php — Form Renderer + +Contains all functions that read JSON schemas and produce HTML. Must contain: + +- Schema loader — reads all schema files from the appropriate directory, sorts by filename, skips malformed files with visible logger call +- Seven field type functions — `text`, `number`, `date`, `boolean`, `select`, `textarea`, `multiselect` +- Field dispatcher — routes to the correct type function, appends diagnostic flag +- Conditional display wrapper — `depends_on` rendered with `data-` attributes +- Diagnostic flag renderer — evaluates `flag_condition` server-side, three severity levels +- Compound condition banner renderer — placeholder returning empty string until orchestrator query implemented +- Cross-VS reference renderer — linked callout +- Verification sources panel — collapsible Bootstrap collapse +- Perspective note renderer — information-record distinction for specific VS codes +- Immutable public record check and read-only renderer + +### {addon}_spool.php — POST Handler + +Contains all functions that handle form submission. Must contain: + +- POST handler — validates CSRF, determines perspective, collects fields +- Required field validator — returns array of error labels +- Spool envelope builder — assembles the contract-compliant JSON payload +- Orchestrator POST — curl call with node token header, returns true on success or error string +- Form re-render with submitted values — called on validation failure ### {addon}.apd @@ -176,11 +211,27 @@ Every field the addon reads from `config.json` must appear here with a descripti "_note": "Copy to config.json. Do not commit config.json — it contains secrets and installation-specific values.", "receiver_url": "REPLACE — orchestrator receiver endpoint", "node_token": "REPLACE — shared secret for node-to-orchestrator auth", - "corpus_builder_group_id": 0, - "listings_file": "REPLACE — absolute path to listings.json on the host" + "listings_file": "REPLACE — absolute path to listings.json on the host", + "directory_default_tab": "core", + "associations": { + "REPLACE-SLUG": { + "_note": "Key is the URL slug — lowercase, hyphens only, matches argv(1) in the route", + "name": "REPLACE — association legal name", + "address": "REPLACE — association street address", + "channel_id": 0, + "channel_address": "REPLACE — channel@node address", + "groups": { + "public": 0, + "sase_participant": 0, + "corpus_builder": 0 + } + } + } } ``` +Multiple associations are registered as additional keyed entries in the `associations` object. No code change is required to add a new association. See ASSOCIATION-CHANNEL-MODEL.md for the full operator workflow. + ### listings.json.template The institutional directory schema. This file documents the structure. The live `listings.json` on the host is operator-managed and never committed to the repo. @@ -189,7 +240,7 @@ The Core tier always contains exactly three slots. They are permanent. They rend ```json { - "_note": "Copy to listings.json on the host. Do not commit listings.json — it contains entity names and approval records that are operator-managed.", + "_note": "Copy to listings.json on the host. Do not commit listings.json.", "_version": "1.0", "core": [ { @@ -238,12 +289,16 @@ Entry schema for Tier-I, Tier-II, and Other: "name": "REPLACE — display name", "description": "REPLACE — one sentence describing this entity's practice", "url": "REPLACE — public URL", + "xchan": "REPLACE — Hubzilla xchan hash of this professional's channel, used for access control", "qualifying_case": "REPLACE — Tier-II attorneys only: case citation from public record", "listed_date": "REPLACE — ISO date of listing", - "approved_date": "REPLACE — ISO date of written approval" + "approved_date": "REPLACE — ISO date of written approval", + "status": "REPLACE — active or pending" } ``` +The `xchan` field is how the addon identifies a professional visitor and grants the `professional` access state. The operator records the xchan hash when approving the listing. Without it, the listing displays in the directory but does not grant professional access. + ### Widget/{Addon}.php The Widget renders the institutional directory in the left aside. This is its only job. @@ -257,11 +312,11 @@ Must contain: - `render_tier_tab($entries, $placeholder)` — renders entries if any exist; renders placeholder text if none - `h($value)` — local HTML escape method -The Widget does not render navigation. It does not render a steps indicator. It does not render resource panels. Those patterns belong to the pilot and do not transfer. +The Widget does not render navigation. It does not render a steps indicator. It does not render resource panels. #### Tab structure -Four horizontal Bootstrap nav-tabs across the top of the aside: +Four horizontal Bootstrap nav-tabs: | Tab | Label | Content | |-----|-------|---------| @@ -270,83 +325,53 @@ Four horizontal Bootstrap nav-tabs across the top of the aside: | Tier-II | Tier-II | Attorneys — qualification standard per INSTITUTIONAL-RELATIONSHIPS.md | | Other | Other | Civic organizations, consumer protection entities, oversight agencies | -The default active tab is Core. The operator may specify a different default per addon in `config.json` via a `directory_default_tab` field — this field is listed in `config.json.template` with value `"core"`. - -A pending Core slot renders: - -```html -