Skip to content
Merged
83 changes: 83 additions & 0 deletions .github/instructions/starmus-boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Repository role and migration state — ADR-034 / ADR-035 / ADR-036

**This repository is designated the Spoken Audio Node** under ADR-034. **It is
not that today**, and this file says plainly what has moved, what has not, and
what each remaining step is waiting on.

Role assignment lives here; the reason lives in the governance registry. Do not
restate the rationale in this repository — cite the record.
Comment on lines +1 to +8

- [ADR-034 — split axis](https://github.com/Starisian-Technologies/sparxstar-architecture-governance-registry/blob/main/standards/decisions/ADR-034-capture-experience-vs-audio-lifecycle-split.md)
- [ADR-035 — capture profiles](https://github.com/Starisian-Technologies/sparxstar-architecture-governance-registry/blob/main/standards/decisions/ADR-035-capture-profiles-not-a-platform-audio-ceiling.md)
- [ADR-036 — elicitation pacing is not acoustic prosody](https://github.com/Starisian-Technologies/sparxstar-architecture-governance-registry/blob/main/standards/decisions/ADR-036-elicitation-pacing-is-not-acoustic-prosody.md)

Seam contracts, which say what actually crosses each boundary:

- [Capture → Ingestion](https://github.com/Starisian-Technologies/sparxstar-architecture-governance-registry/blob/main/contracts/spoken-audio-capture-to-ingestion.md)
- [Asset → Records](https://github.com/Starisian-Technologies/sparxstar-architecture-governance-registry/blob/main/contracts/spoken-audio-asset-to-records.md)

## What this repository will own

Server-side ingestion, validation, integrity, immutable object storage,
derivatives, quality measurements, bulk import, processing jobs, and authorized
consumption. A service, not a set of pages.

**It does not capture.** Browser microphone access, local and offline handling,
the chunked-upload client and capture UX are the capture UI package's. The
reviewed transcript and its translation are ESU's; acoustic measurement is
ours, and interpreting what a measurement means is ESU's.

## What it stops owning

CMS templates and shortcodes · CMS admin screens · custom-post-type and
attachment persistence · a CMS custom-field plugin as its primary database ·
browser recorder code · CSS and frontend rendering · transcript review ·
prosodic interpretation.

## Migration state, honestly

### Done

- The **manifest-driven UI package boundary** exists
(`starmus-ui-packages.json`, `StarmusUiPackageResolver`). This is the
mechanism that will let the plugin stop shipping its own copy of the capture
JS and consume the built capture UI package instead. It is a precondition for
everything below, and it landed.
- The capture UI package has removed the paced reader, the transcript-sync
controller, and the platform-wide audio ceiling.

### Blocked, and on what

1. **Removing the duplicated capture JS from `src/js/`.** Nineteen files, of
which fifteen are diverged copies of files in the capture UI package. One
home per capture-path source file (ADR-034). **Blocked on** the capture UI
package being resolvable through the manifest as a published artifact rather
than a sibling checkout. Deleting them before then breaks a shipping
product.
2. **Removing the prosody assets** (`src/js/prosody/`,
`src/css/starmus-prosody-engine.css` and their built forms) and the
`prosodyScript` / `prosodyStyle` manifest surface. **Blocked on** the
elicitation pacing package being publishable: `StarmusProsodyPlayer.php`
enqueues these today, and the resolver needs a URL to point at instead.
Deleting the assets first takes the paced reader off every live page.
3. **Standing up the ingestion service itself.** **Blocked on** the items the
Capture → Ingestion contract lists as *owed*: the endpoint path and auth
model, the upload metadata key set, the acknowledgement and error envelope,
and confirmation that the consumer verifies `sha256` as the producer sends.
**No repository implements a guess at those.** Writing endpoint paths, field
names or status codes for a service that does not exist is how fabricated
identifiers ship, and this organisation has shipped them before.
4. **Which repository carries the CMS host product** once this one becomes a
service — this repo's residue, or a new one. Explicitly left open by
ADR-034. **Decide it before moving files, not during.**

## Rules that already bind this repository

- **No platform-wide audio ceiling** (ADR-035). A capture profile travels with
the asset; an asset carrying none is *stored* and merely not admissible as a
source for acoustic measurement — never rejected, because unconditional
capture still holds.
- **No transcoding on the ingestion path.** `import` means the received bytes
are preserved. Derivatives are additional objects.
- **Never hold the transcript of record.** A machine transcript may exist as a
processing artifact of an asset, marked unreviewed. Consumers read ESU.
30 changes: 15 additions & 15 deletions src/core/StarmusAssetLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -413,21 +413,21 @@ private function build_bootstrap_inline_script(array $config): string
$base_json = (string) wp_json_encode($base, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);

return <<<JS
// The asset loader emits the authoritative runtime bootstrap immediately before this
// bundle executes. Page-surface fields written to window.STARMUS_BOOTSTRAP earlier
// in the document are preserved; base fields fill any unset slots.
(function () {
var base = {$base_json};
var page = window.STARMUS_BOOTSTRAP || {};
base.pageType = page.pageType || base.pageType;
base.mode = page.mode || base.mode;
base.postId = page.postId !== undefined ? page.postId : base.postId;
base.canCommit = page.canCommit !== undefined ? page.canCommit : base.canCommit;
base.artifact = page.artifact || base.artifact;
base.hosts = page.hosts !== undefined ? page.hosts : base.hosts;
window.STARMUS_BOOTSTRAP = base;
}());
JS;
// The asset loader emits the authoritative runtime bootstrap immediately before this
// bundle executes. Page-surface fields written to window.STARMUS_BOOTSTRAP earlier
// in the document are preserved; base fields fill any unset slots.
(function () {
var base = {$base_json};
var page = window.STARMUS_BOOTSTRAP || {};
base.pageType = page.pageType || base.pageType;
base.mode = page.mode || base.mode;
base.postId = page.postId !== undefined ? page.postId : base.postId;
base.canCommit = page.canCommit !== undefined ? page.canCommit : base.canCommit;
base.artifact = page.artifact || base.artifact;
base.hosts = page.hosts !== undefined ? page.hosts : base.hosts;
window.STARMUS_BOOTSTRAP = base;
}());
JS;
}

private function resolve_bootstrap_mode(): string
Expand Down
33 changes: 15 additions & 18 deletions src/core/StarmusUiPackageResolver.php
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
<?php

declare(strict_types=1);

namespace Starisian\Sparxstar\Starmus\core;

use Starisian\Sparxstar\Starmus\helpers\StarmusLogger;

use function file_exists;
use function file_get_contents;
use function filemtime;
use function is_array;
use function is_string;
use function json_decode;

Comment on lines 6 to 10
use Starisian\Sparxstar\Starmus\helpers\StarmusLogger;

final class StarmusUiPackageResolver
{
private const MANIFEST_FILE = 'starmus-ui-packages.json';
Expand Down Expand Up @@ -47,16 +44,16 @@ public function resolve_asset(string $asset_key): array
{
$manifest = $this->load_manifest();
$assets = $manifest['assets'] ?? [];
$asset = is_array($assets) && isset($assets[$asset_key]) && is_array($assets[$asset_key])
$asset = \is_array($assets) && isset($assets[$asset_key]) && \is_array($assets[$asset_key])
? $assets[$asset_key]
: [];

$handle = is_string($asset['handle'] ?? null) ? $asset['handle'] : '';
$type = is_string($asset['type'] ?? null) ? $asset['type'] : 'script';
$package = is_string($asset['package'] ?? null) ? $asset['package'] : '';
$surface = is_string($asset['surface'] ?? null) ? $asset['surface'] : '';
$handle = \is_string($asset['handle'] ?? null) ? $asset['handle'] : '';
$type = \is_string($asset['type'] ?? null) ? $asset['type'] : 'script';
$package = \is_string($asset['package'] ?? null) ? $asset['package'] : '';
$surface = \is_string($asset['surface'] ?? null) ? $asset['surface'] : '';

$external_url = is_string($asset['externalUrl'] ?? null) ? $asset['externalUrl'] : '';
$external_url = \is_string($asset['externalUrl'] ?? null) ? $asset['externalUrl'] : '';
if ($external_url !== '') {
return [
'handle' => $handle,
Expand All @@ -68,7 +65,7 @@ public function resolve_asset(string $asset_key): array
];
}

$min_rel = is_string($asset['min'] ?? null) ? $asset['min'] : '';
$min_rel = \is_string($asset['min'] ?? null) ? $asset['min'] : '';
if ($min_rel !== '' && $this->base_path !== '' && file_exists($this->base_path . $min_rel)) {
return [
'handle' => $handle,
Expand All @@ -80,7 +77,7 @@ public function resolve_asset(string $asset_key): array
];
}

$src_rel = is_string($asset['src'] ?? null) ? $asset['src'] : '';
$src_rel = \is_string($asset['src'] ?? null) ? $asset['src'] : '';
if ($src_rel !== '' && $this->base_path !== '' && file_exists($this->base_path . $src_rel)) {
return [
'handle' => $handle,
Expand Down Expand Up @@ -110,7 +107,7 @@ public function get_runtime_projection(): array
$manifest = $this->load_manifest();
$runtime = $manifest['runtime'] ?? [];

return is_array($runtime) ? $runtime : [];
return \is_array($runtime) ? $runtime : [];
}

private function resolve_version(): string
Expand All @@ -132,22 +129,22 @@ private function load_manifest(): array
return $this->manifest;
}

$normalized_base_path = \rtrim($this->base_path, '/\\');
$manifest_path = $normalized_base_path . DIRECTORY_SEPARATOR . self::MANIFEST_FILE;
$normalized_base_path = rtrim($this->base_path, '/\\');
$manifest_path = $normalized_base_path . DIRECTORY_SEPARATOR . self::MANIFEST_FILE;
Comment thread
Copilot marked this conversation as resolved.

if ( ! file_exists($manifest_path)) {
$this->manifest = [];
return $this->manifest;
}

$manifest_json = file_get_contents($manifest_path);
if ( ! is_string($manifest_json) || $manifest_json === '') {
if ( ! \is_string($manifest_json) || $manifest_json === '') {
$this->manifest = [];
return $this->manifest;
}

$decoded = json_decode($manifest_json, true);
if ( ! is_array($decoded)) {
if ( ! \is_array($decoded)) {
StarmusLogger::error(
'[StarmusUiPackageResolver] Failed to parse manifest: ' . $manifest_path,
['json_error' => json_last_error_msg()]
Expand Down
3 changes: 1 addition & 2 deletions src/frontend/StarmusProsodyPlayer.php
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
<?php

declare(strict_types=1);

namespace Starisian\Sparxstar\Starmus\frontend;

use function class_exists;
use function ob_get_clean;
use function ob_start;

use Starisian\Sparxstar\Starmus\data\StarmusProsodyDAL;
use Starisian\Sparxstar\Starmus\core\StarmusUiPackageResolver;
use Starisian\Sparxstar\Starmus\data\StarmusProsodyDAL;
use Starisian\Sparxstar\Starmus\helpers\StarmusLogger;
use Throwable;

Expand Down
74 changes: 13 additions & 61 deletions src/js/starmus-integrator.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,7 @@
* @global
* @namespace
*/
window.Starmus =
window.Starmus ||
{
/* intentionally empty */
};
window.Starmus = window.Starmus || {/* intentionally empty */};

/**
* Current version of the Starmus integration layer.
Expand Down Expand Up @@ -75,31 +71,11 @@ window.addEventListener("sparxstar:environment-ready", (e) => {
return;
}

const raw =
e.detail ||
{
/* intentionally empty */
};
const tech =
raw.technical ||
{
/* intentionally empty */
};
const rawTech =
tech.raw ||
{
/* intentionally empty */
};
const profile =
tech.profile ||
{
/* intentionally empty */
};
const idents =
raw.identifiers ||
{
/* intentionally empty */
}; // Sometimes at root
const raw = e.detail || {/* intentionally empty */};
const tech = raw.technical || {/* intentionally empty */};
const rawTech = tech.raw || {/* intentionally empty */};
const profile = tech.profile || {/* intentionally empty */};
const idents = raw.identifiers || {/* intentionally empty */}; // Sometimes at root
// Handle case where identifiers might be inside technical or separate (based on logs)

/**
Expand All @@ -118,37 +94,21 @@ window.addEventListener("sparxstar:environment-ready", (e) => {
const normalizedEnv = {
// 1. Device Info (Merge Detector + Profile)
device: {
...(rawTech.device ||
{
/* intentionally empty */
}),
...(rawTech.device || {/* intentionally empty */}),
class: profile.deviceClass || "unknown",
os:
raw.identifiers?.deviceDetails?.os ||
{
/* intentionally empty */
},
os: raw.identifiers?.deviceDetails?.os || {/* intentionally empty */},
userAgent: navigator.userAgent,
},

// 2. Browser Info
browser: {
...(rawTech.browser ||
{
/* intentionally empty */
}),
...(raw.identifiers?.deviceDetails?.client ||
{
/* intentionally empty */
}),
...(rawTech.browser || {/* intentionally empty */}),
...(raw.identifiers?.deviceDetails?.client || {/* intentionally empty */}),
},

// 3. Network Info
network: {
...(rawTech.network ||
{
/* intentionally empty */
}),
...(rawTech.network || {/* intentionally empty */}),
profile: profile.networkProfile || "unknown",
},

Expand All @@ -161,16 +121,8 @@ window.addEventListener("sparxstar:environment-ready", (e) => {

// 5. Features / Battery / Perf
features: {
battery:
rawTech.battery ||
{
/* intentionally empty */
},
performance:
rawTech.performance ||
{
/* intentionally empty */
},
battery: rawTech.battery || {/* intentionally empty */},
performance: rawTech.performance || {/* intentionally empty */},
},

// 6. Init Error Array (Required by Schema)
Expand Down
4 changes: 1 addition & 3 deletions src/js/starmus-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,9 +320,7 @@ document.addEventListener("DOMContentLoaded", () => {
}

const instanceId =
bootstrap.hosts.formId ||
recorderForm.getAttribute("data-starmus-instance") ||
"";
bootstrap.hosts.formId || recorderForm.getAttribute("data-starmus-instance") || "";
initRecorderInstance(recorderForm, instanceId, bootstrap);
} else if (bootstrap.pageType === "editor") {
const editorRoot = document.getElementById(bootstrap.hosts.editorRootId || "");
Expand Down
16 changes: 3 additions & 13 deletions src/js/starmus-recorder.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,7 @@ class LanguageSignalAnalyzer {
country: country,
probe_language: this.probeLanguages[0] || null,
},
violation_flags: {
/* intentionally empty */
},
violation_flags: {/* intentionally empty */},
timing_hints: [],
};

Expand Down Expand Up @@ -456,11 +454,7 @@ function initRecorder(store, instanceId) {
type: "starmus/calibration-update",
message: msg,
volumePercent: vol,
extra:
extra ||
{
/* intentionally empty */
},
extra: extra || {/* intentionally empty */},
});
}
},
Expand Down Expand Up @@ -521,11 +515,7 @@ function initRecorder(store, instanceId) {

// Get optimized settings from SPARXSTAR
const envData = sparxstarIntegration.getEnvironmentData();
const settings =
envData.recordingSettings ||
{
/* intentionally empty */
};
const settings = envData.recordingSettings || {/* intentionally empty */};

// Apply tier-based audio constraints
const audioConstraints = {
Expand Down
Loading