From 3640bd1e01ad0a922d835316bb6e58b800306bd1 Mon Sep 17 00:00:00 2001 From: TheRON Date: Fri, 19 Jun 2026 04:53:26 -0400 Subject: [PATCH] Updated --- hubzilla/addon/scn01/scn01_spool.php | 119 +++++++++++++++++++++++---- 1 file changed, 103 insertions(+), 16 deletions(-) diff --git a/hubzilla/addon/scn01/scn01_spool.php b/hubzilla/addon/scn01/scn01_spool.php index 2172b5d..55e5199 100644 --- a/hubzilla/addon/scn01/scn01_spool.php +++ b/hubzilla/addon/scn01/scn01_spool.php @@ -2,7 +2,7 @@ /** * SCN-01 Spool Handler - * Validates POST submissions, builds the spool envelope, + * Validates POST submissions, builds the spool envelope with VS and DSC snapshots, * and POSTs to the orchestrator receiver. * Records are immutable — no edit path. */ @@ -35,19 +35,42 @@ function scn01_handle_post($association_slug, $access) { return $out; } + // Load association config from vs01 (shared source of association data) $cfg_raw = @file_get_contents('addon/vs01/config.json'); $cfg = $cfg_raw ? json_decode($cfg_raw, true) : []; $assoc = $cfg['associations'][$association_slug] ?? []; + // Load scn01 config for receiver_url and node_token + $scn_config = scn01_load_config(); + + // Load VS and DSC snapshots for this participant. + // These are embedded in the Scenario envelope — the record must be self-contained. + $participant_id = scn01_get_participant_id(); + $vs_snapshot = scn01_load_vs_snapshot($participant_id, $association_slug); + $dsc_snapshot = scn01_load_dsc_snapshot($participant_id, $association_slug); + + // Prerequisite gate: both VS and DSC sets must exist before a Scenario can be submitted. + if (empty($vs_snapshot) || empty($dsc_snapshot)) { + return '
+ Please complete your Vital Signs and DSC Category records first. + Your Vital Signs and DSC records establish the context for any Scenario you submit. + Complete Vital Signs | + Complete DSC Categories +
'; + } + $envelope = scn01_build_spool_envelope( $narrative, $pinned, $association_slug, $assoc['channel_id'] ?? '', - $access + $access, + $vs_snapshot, + $dsc_snapshot, + $scn_config['node_token'] ?? '' ); - $result = scn01_post_to_orchestrator($envelope); + $result = scn01_post_to_orchestrator($envelope, $scn_config); if ($result === true) { $assoc_name = scn01_h($assoc['name'] ?? $association_slug); @@ -71,19 +94,81 @@ function scn01_handle_post($association_slug, $access) { } // --------------------------------------------------------------------------- -// SPOOL ENVELOPE +// SNAPSHOT LOADERS +// Read the participant's current VS and DSC records from the orchestrator's +// stored JSON files. These are embedded verbatim in the Scenario envelope. // --------------------------------------------------------------------------- -function scn01_build_spool_envelope($narrative, $pinned_scenario_ids, $association_slug, $channel_id, $standing) { +function scn01_load_vs_snapshot($participant_id, $association_slug) { + if (!$participant_id || !$association_slug) return []; + // VS records are stored by the orchestrator at a known path. + // On the Hubzilla node we read the stored record via the orchestrator's + // GET endpoint — but since there is no GET endpoint yet, we read + // from pconfig as the interim source. + // When a GET /vs01/record endpoint is added to the orchestrator, + // this function should be updated to call it instead. + $stored = get_pconfig(local_channel(), 'vs01_snapshot', $association_slug); + if ($stored) { + $data = json_decode($stored, true); + if (json_last_error() === JSON_ERROR_NONE) return $data; + } + return []; +} + +function scn01_load_dsc_snapshot($participant_id, $association_slug) { + if (!$participant_id || !$association_slug) return []; + $stored = get_pconfig(local_channel(), 'dsc01_snapshot', $association_slug); + if ($stored) { + $data = json_decode($stored, true); + if (json_last_error() === JSON_ERROR_NONE) return $data; + } + return []; +} + +// --------------------------------------------------------------------------- +// PARTICIPANT ID +// --------------------------------------------------------------------------- + +function scn01_get_participant_id() { + $observer = get_observer_hash(); + if (!$observer) return ''; + + $r = q("SELECT xchan_addr FROM xchan WHERE xchan_hash = '%s' LIMIT 1", + dbesc($observer) + ); + if (!$r) return ''; + + $addr = $r[0]['xchan_addr'] ?? ''; + $parts = explode('@', $addr); + return $parts[0] ?? ''; +} + +// --------------------------------------------------------------------------- +// SPOOL ENVELOPE +// Matches contracts/scn01/record-v1.json — _header / _payload / _credentials. +// --------------------------------------------------------------------------- + +function scn01_build_spool_envelope($narrative, $pinned_scenario_ids, $association_slug, $channel_id, $standing, $vs_snapshot, $dsc_snapshot, $node_token) { return [ - 'addon' => 'scn01', - 'contract_version' => '1.0', - 'association_slug' => $association_slug, - 'association_channel_id' => (string) $channel_id, - 'submitted_at' => date('c'), - 'standing' => $standing, - 'pinned_scenario_ids' => $pinned_scenario_ids, - 'narrative' => $narrative, + '_header' => [ + 'addon' => 'scn01', + 'contract_version' => '1.0', + 'association_slug' => $association_slug, + 'association_channel_id' => (string) $channel_id, + 'participant_id' => scn01_get_participant_id(), + 'submitted_at' => date('c'), + 'standing' => $standing, + 'node_token_hash' => hash('sha256', $node_token), + ], + '_payload' => [ + 'pinned_scenario_ids' => $pinned_scenario_ids, + 'narrative' => $narrative, + 'vs_snapshot' => $vs_snapshot, + 'dsc_snapshot' => $dsc_snapshot, + ], + '_credentials' => [ + 'g1_tx_hash' => null, // populated when Ğ1 payment is wired + ], ]; } @@ -91,10 +176,12 @@ function scn01_build_spool_envelope($narrative, $pinned_scenario_ids, $associati // ORCHESTRATOR POST // --------------------------------------------------------------------------- -function scn01_post_to_orchestrator($envelope) { - $config = scn01_load_config(); +function scn01_post_to_orchestrator($envelope, $config = null) { + if ($config === null) { + $config = scn01_load_config(); + } $receiver_url = $config['receiver_url'] ?? ''; - $node_token = $config['node_token'] ?? ''; + $node_token = $config['node_token'] ?? ''; if (!$receiver_url) { logger('scn01_spool: receiver_url not configured');