@kieransimkin/dancemoves 3.1.22 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # DanceMoves
2
2
 
3
+ **3.2.0** adds native, non-looping playback of StemLab/DanceRudiments audio-derived movement recordings. `runtime.recordings` composes the existing audio clock, shared player mapping, accessibility preferences and lifecycle. Captured BPM must match page metadata; samples retain their original integer-pip clock and never wrap. See [recording playback](docs/audio-recording-playback.md).
4
+
5
+
6
+ **3.1.23** secures WordPress motion-capture writes with authenticated
7
+ `manage_options` authorization. The private capture administration UI uses the
8
+ same capability. Published tokens no longer authorize uploads; cookie clients
9
+ send a fresh WordPress REST nonce and same-origin credentials, and administrators
10
+ have separate bounded upload quotas. Local recording/download stays available
11
+ without upload authorization. See the [capture contract](docs/api/wordpress.md#motion-capture-rest-api).
12
+ The real WordPress integration test reports its executed assertion count;
13
+ CI rejects a missing or zero-count summary as well as any failed check.
14
+
3
15
  **3.1.22** fixes WordPress.org submission metadata: the Author URI points to
4
16
  [Kieran's website](https://kieransimkin.co.uk/), separately from the plugin
5
17
  homepage. The release checker rejects duplicate plugin/author URLs before
@@ -602,7 +614,8 @@ const example = {schema:'ks-epk-motion-recording/v1', samples:[{t:0,beta:10,gamm
602
614
  inspect(example);
603
615
  // The local simulator uses /examples/wordpress/api/capture, never /wp-json/… .
604
616
  // For actual captures, follow docs/api/wordpress.md and the private sensor harness.
605
- // The existing source token is not strong authentication; never embed it in public examples.
617
+ // Uploads require administrator authorization and a fresh WordPress REST nonce.
618
+ // Keep real session nonces out of static examples and source control.
606
619
  ```
607
620
 
608
621
  [Run the example](examples/wordpress/capture.html) · [Read its complete source](examples/wordpress/demos/capture.mjs).
@@ -7,6 +7,16 @@
7
7
  "effects_version": "3.1.13",
8
8
  "effects_reviewed_on": "2026-10-08",
9
9
  "inventory_method": "Manual comparison with the pinned source exports and PHP declarations; symbol-presence validation is not runtime coverage.",
10
+ "wordpress_capture_security": {
11
+ "version": "3.1.23",
12
+ "reviewed_on": "2026-10-15",
13
+ "scope": "Current capture contract supersedes the historical 2.8.0 capture authorization, quota and post-type capability description; pinned baseline symbols and source hashes below remain historical evidence.",
14
+ "authorization": "WordPress-authenticated current_user_can('manage_options'); cookie REST nonces are checked by WordPress core",
15
+ "removed_constants": ["KS_EPK_MOTION_CAPTURE_TOKEN"],
16
+ "quota": "ks_motion_user_<authenticated user ID>, eight accepted checks per ten-minute expiry",
17
+ "private_capture_capability": "manage_options",
18
+ "reference": "wordpress.md#motion-capture-rest-api"
19
+ },
10
20
  "source_blobs": {
11
21
  "kieran-epk-device-orientation.php": "b374690a0c141aa1f870bf1084a0a0b34c25abe5",
12
22
  "assets/dance-moves-core.js": "3d975c7a3b380f9c99a62f7deade14df4b746929",
@@ -15,7 +15,7 @@ registration, rendering or save callbacks manually.
15
15
  | --- | --- |
16
16
  | `DANCE_MOVES_VERSION` | `2.8.0` |
17
17
  | `KS_EPK_ORIENTATION_VERSION` | Alias of `DANCE_MOVES_VERSION`. |
18
- | `KS_EPK_MOTION_CAPTURE_TOKEN` | Existing fixed capture submission token; deliberately not reproduced here. It is in public source and is **not a deployment secret**. See the capture security section. |
18
+ | `KS_EPK_MOTION_CAPTURE_TOKEN` | Removed in 3.1.23. No fixed/published token authorizes capture writes. See the current capture security section. |
19
19
  | `DANCE_MOVES_META_BPM` | `_dance_moves_bpm` |
20
20
  | `DANCE_MOVES_META_LYRIC_TIMING` | `_dance_moves_lyric_timing_id` |
21
21
  | `DANCE_MOVES_META_CUE_TIMING` | `_dance_moves_cue_timing_id` |
@@ -172,7 +172,7 @@ The full validator runs when the Page editor saves the selected attachment.
172
172
  ```text
173
173
  POST <WordPress REST root>/ks-epk-motion/v1/capture
174
174
  Content-Type: application/json
175
- X-KS-Motion-Token: <operator-supplied value; not reproduced here>
175
+ X-WP-Nonce: <fresh wp_rest nonce for the signed-in administrator session>
176
176
  ```
177
177
 
178
178
  Implemented by `ks_epk_motion_register_rest_route()` using
@@ -180,23 +180,48 @@ Implemented by `ks_epk_motion_register_rest_route()` using
180
180
  `ks_epk_motion_capture_permission(WP_REST_Request $request)` and handler
181
181
  `ks_epk_motion_store_capture(WP_REST_Request $request)`.
182
182
 
183
- Permission processing, in order:
183
+ In 3.1.23, permission processing is:
184
+
185
+ 1. WordPress authenticates the request before the permission callback. Cookie
186
+ clients send same-origin credentials and a fresh `wp_rest` nonce in
187
+ `X-WP-Nonce`; WordPress validates that nonce. Missing/expired cookie nonces
188
+ cannot establish administrator authorization. Standard WordPress Application
189
+ Password authentication over HTTPS may also establish the current user;
190
+ this plugin does not create or store those credentials.
191
+ 2. Require `current_user_can('manage_options')`. Anonymous requests return 401;
192
+ authenticated callers lacking that capability return 403. The former
193
+ `X-KS-Motion-Token` header is ignored and its public constant is removed.
194
+ 3. When `get_http_origin()` supplies an origin, require it to equal `home_url()`
195
+ after trailing-slash removal. A missing Origin is allowed only after genuine
196
+ WordPress authentication/authorization, for example an authorized API client.
197
+ 4. Rate-limit by authenticated user ID, using `ks_motion_user_<id>`. Eight
198
+ accepted permission checks are allowed before 429. Each allowed check writes
199
+ a ten-minute expiry; a later-invalid payload still consumes a check. Denied
200
+ callers and other administrators cannot exhaust an administrator's quota.
201
+
202
+ The development lab's static HTML carries no real nonce. Populate its
203
+ `ksMotionCaptureConfig.restNonce` from the current administrator session, not a
204
+ committed value. A registered harness on an authenticated WordPress admin screen
205
+ can use the normal script configuration route:
184
206
 
185
- 1. Compare the supplied header with the fixed token using `hash_equals()`.
186
- 2. When `get_http_origin()` returns an origin, require it to equal `home_url()`
187
- after trailing-slash removal. A missing Origin is **not rejected**.
188
- 3. Rate-limit by a transient keyed from MD5 of `REMOTE_ADDR` (or `'unknown'`).
189
- Eight accepted permission checks are allowed before a 429. Each allowed check
190
- writes a new ten-minute expiry; this is not a rigorously sliding-window quota.
191
- A later-invalid payload still consumes a permission-check attempt.
207
+ ```php
208
+ if (current_user_can('manage_options')) {
209
+ // Run after registering/enqueuing your own capture harness script.
210
+ wp_localize_script('your-capture-harness', 'ksMotionCaptureConfig', array(
211
+ 'endpoint' => esc_url_raw(rest_url('ks-epk-motion/v1/capture')),
212
+ 'restNonce' => wp_create_nonce('wp_rest'),
213
+ ));
214
+ }
215
+ ```
192
216
 
193
- **Security limitation:** the token is embedded in this public repository. The
194
- endpoint does not require a logged-in user, a WordPress capability or a WordPress
195
- nonce. Origin checking and a shared IP transient do not turn a published constant
196
- into authentication. Do not document this as a secure secret-protected upload
197
- service. Restrict/disable access or implement genuine authenticated capture
198
- submission before relying on it in a new deployment. This documentation patch
199
- does not change the endpoint's security model or rotate its constant.
217
+ The capture client sends `credentials: 'same-origin'` and rejects a different
218
+ endpoint origin before transmitting a nonce or recording. A missing nonce leaves
219
+ local recording/download available and displays an accurate upload hold. After
220
+ a session/nonce expires, reload the authorized configuration before retrying.
221
+ Sensor consent and the explicit upload button remain separate requirements.
222
+ See WordPress's [REST authentication](https://developer.wordpress.org/rest-api/using-the-rest-api/authentication/)
223
+ and [permission callback](https://developer.wordpress.org/rest-api/extending-the-rest-api/adding-custom-endpoints/#permissions-callback)
224
+ documentation. No custom authentication mechanism is added.
200
225
 
201
226
  ### Request shape
202
227
 
@@ -275,9 +300,9 @@ Success returns a normal REST response (no explicit 201 status is set):
275
300
 
276
301
  | HTTP status | Error code | Meaning |
277
302
  | --- | --- | --- |
278
- | 403 | `ks_motion_forbidden` | Missing/wrong token. |
303
+ | 401 / 403 | `ks_motion_forbidden` | WordPress caller is anonymous / lacks `manage_options`. |
279
304
  | 403 | `ks_motion_origin` | Supplied origin does not match the site. |
280
- | 429 | `ks_motion_rate` | Permission-check quota exhausted. |
305
+ | 429 | `ks_motion_rate` | This administrator's permission-check quota exhausted. |
281
306
  | 400 | `ks_motion_schema` | Missing/wrong schema or nonarray JSON payload. |
282
307
  | 400 | `ks_motion_samples` | Missing/nonarray samples or input count outside 1–500. |
283
308
  | 400 | `ks_motion_invalid_samples` | At least one required sample field is invalid. |
@@ -292,9 +317,12 @@ A successful record is a `private` post of type `ks_motion_capture`, titled
292
317
  `Phone motion capture <site-local date/time>`, with the cleaned JSON in
293
318
  `post_content`. Extra metadata: `_ks_motion_passed` (`'1'`/`'0'`),
294
319
  `_ks_motion_sample_count`, and `_ks_motion_sha256` (hash of stored encoded JSON).
295
- The post type has a Tools-menu UI, title/editor support, `public: false`,
296
- `show_in_rest: false`, and default mapped post capabilities; no custom
297
- administrator-only capability scheme is defined.
320
+ The post author is the authenticated administrator ID. The post type has a
321
+ Tools-menu UI, title/editor support, `public: false`, and `show_in_rest: false`.
322
+ In 3.1.23 its read, create, edit and delete capabilities all require
323
+ `manage_options`, with generic post meta-capability mapping disabled. Ordinary
324
+ editors/authors cannot acquire access to private sensor captures through their
325
+ normal post-editing capabilities. Retention remains an explicit operator duty.
298
326
 
299
327
  There is **no plugin GET/list/delete capture REST route, retention job, automatic
300
328
  capture upload, or cryptographic proof of native sensor provenance**. Private
@@ -323,6 +351,24 @@ available. Do not assume a root exists merely because the shared script loaded.
323
351
 
324
352
  ## Registered hooks and callback reference
325
353
 
354
+ ### Project prefixes and compatibility
355
+
356
+ Production PHP declarations use `dance_moves_` / `ks_epk_` functions and
357
+ `DANCE_MOVES_` / `KS_EPK_` constants. Plugin-owned metadata, transients, post
358
+ types, REST namespaces and asset handles use the existing `dance_moves`,
359
+ `dance-moves`, `ks_motion`, `ks-epk-motion` or `ks-epk-device-orientation`
360
+ prefixes. Localized JavaScript configuration uses the named `danceMoves*` and
361
+ `ksEpkOrientationConfig` objects. WordPress core hook names in the table below
362
+ are consumed hooks; they must retain their core names.
363
+
364
+ These names remain for installed-site compatibility. In 3.1.23 the obsolete
365
+ public token constant is removed and the authenticated quota uses the prefixed
366
+ `ks_motion_user_<id>` transient. The declaration-prefix regression and capture
367
+ authorization/client tests run in the existing legacy test gate. Hosted CI also
368
+ exercises real WordPress core nonce validation, roles, REST dispatch, database
369
+ storage and capability mapping on a disposable site. It does not use a live
370
+ installation or claim physical phone acceptance.
371
+
326
372
  These are WordPress hooks **consumed** by the plugin, not plugin-defined extension
327
373
  hooks. Default priority is 10 unless shown. No `apply_filters('dance_moves_...')`
328
374
  or `do_action('dance_moves_...')` extension contract is exposed in this baseline.
@@ -0,0 +1,56 @@
1
+ # Audio-derived recording playback
2
+
3
+ DanceMoves 3.2.0 adds `runtime.recordings` for the bounded `dancerudiments.recording`
4
+ v1 JSON format exported by StemLab and DanceRudiments 0.3.0. Named native patterns
5
+ continue to loop at their own periods. Recordings retain their captured origin,
6
+ 64-pip resolution, named dimensions and exact extent; they never wrap or interpolate.
7
+
8
+ ```js
9
+ const recording = await runtime.recordings.load('/approved-motion.json');
10
+ const motion = runtime.recordings.animate({
11
+ id: 'subject:recording', root, target, audio,
12
+ recording,
13
+ render({values, reason}) {
14
+ // Map bounded native samples to the existing subject's appearance.
15
+ // A CSSOM rule avoids high-frequency inline-style mutation observers.
16
+ rule.style.setProperty('--subject-angle', `${values[0] * 6}deg`);
17
+ }
18
+ });
19
+ await motion.ready;
20
+ motion.setEnabled(false); // neutral values, preserving the captured clock
21
+ motion.setEnabled(true);
22
+ motion.destroy();
23
+ ```
24
+
25
+ The controller composes the existing native DanceMoves audio scheduler and its
26
+ shared-player mapping. Pause holds the source pose; seeking reconstructs it.
27
+ Reduced motion, forced colours and explicit disable output zeroes. Hidden and
28
+ offscreen subjects suspend through the existing lifecycle. Positions outside the
29
+ recording emit zeroes with `reason: 'outside-recording'`; seeking back into the
30
+ capture resumes its exact native sample. Runtime disposal destroys all recording
31
+ controllers, including pending native loads. Use `pause()`, `resume()`, `refresh()`,
32
+ `snapshot()`, `recordings.get(id)` and `recordings.snapshot()` for ownership and QA.
33
+
34
+ The recording BPM must match the authoritative runtime/page BPM. A controller
35
+ cannot override tempo, rate, phase or clock. BPM provenance and phase provenance
36
+ remain separate. The page's WordPress EPK Timing metadata remains the only source
37
+ for lyric/cue attachments; a movement recording never replaces those fields.
38
+ Use the exact track/master, same-clock stem provenance and original capture origin.
39
+ StemLab owns separation, filtering and smoothing; this player performs no DSP.
40
+ All smoothing variants should use a fixed reference scale for a fair comparison.
41
+
42
+ The sampler is the official native C++/WebAssembly accessor from DanceRudiments
43
+ 0.3.0. There is no JavaScript movement fallback. Its embedded binary SHA-256 is
44
+ `d4cd976156860aa7a5c9665b27d2fda3cf6a0fa0da3dc0b9df365e425f1c6e29`, built from
45
+ upstream `include/dancerudiments/recording_view.hpp` SHA-256
46
+ `19dd73a150a82c4d9074fe4b9ba4c2f09d41e206028a6a6dc922d94e00dc4b8d` and
47
+ `tools/recording_wasm.cpp` SHA-256
48
+ `f4713cc7c773fa065715383fc68dccd91398fff08cd89167c5f24e64c0cecea9`.
49
+ See the [upstream recording contract](https://github.com/kieransimkin/DanceRudiments/blob/v0.3.0/docs/recordings.md).
50
+ The existing bundled named catalogue and its separate version are unchanged.
51
+
52
+ Inputs are limited to 64 MiB JSON, 8,388,608 finite values in [-1,1], 1..64 unique
53
+ named dimensions and signed 32-bit integer pip bounds. Loading fails closed for
54
+ bad UTF-8, schema, samples, HTTP responses, mismatched BPM or unavailable WASM.
55
+ Treat `ready` rejection as an unavailable motion state and retain static content.
56
+ These source/build changes do not deploy a WordPress plugin or page.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema": "dancemoves-library-build/v1",
3
3
  "name": "@kieransimkin/dancemoves",
4
- "version": "3.1.22",
4
+ "version": "3.2.0",
5
5
  "homepage": "https://kieransimkin.co.uk/my-songs/",
6
6
  "files": [
7
7
  {
@@ -31,23 +31,23 @@
31
31
  },
32
32
  {
33
33
  "path": "dancemoves.min.js",
34
- "bytes": 212321,
35
- "sha256": "36651cd57003477609ac0024c66ae69698cf51ef1ae083f3dd25e7a7ad41e3c4"
34
+ "bytes": 218383,
35
+ "sha256": "9a9afad98cbf15d3c8394a54809596d1ffe784f5ac7ca13484186ace3eb93661"
36
36
  },
37
37
  {
38
38
  "path": "index.cjs",
39
- "bytes": 361185,
40
- "sha256": "1f02fb91fa046ac132a0757ceed994843adae20728fbb80327a1ea6c314a515a"
39
+ "bytes": 370576,
40
+ "sha256": "ca7ed8da52270ee4a17f94672121f4182fa5cc8c5964c8d095a682978d1b4981"
41
41
  },
42
42
  {
43
43
  "path": "index.d.ts",
44
- "bytes": 13718,
45
- "sha256": "82ec16610754f0237cd91e2a7a7ce4f6fe17617d8a61b78d089143e9fdb142e4"
44
+ "bytes": 14929,
45
+ "sha256": "477a8d665f245dbcff92cdc17372f39b54dbe3f9392695a00773f43b92288914"
46
46
  },
47
47
  {
48
48
  "path": "index.mjs",
49
- "bytes": 360040,
50
- "sha256": "1766fb4365f13191b75dc77ac01c9e51c8e8250e8408c34a7d7bf19ce77fe32b"
49
+ "bytes": 369431,
50
+ "sha256": "6edacb0ded30311fc26bb80e86c772ba362c1b60c54a68b5ebaa2d3eb484ac16"
51
51
  },
52
52
  {
53
53
  "path": "react.cjs",
@@ -106,8 +106,8 @@
106
106
  },
107
107
  {
108
108
  "path": "wordpress.js",
109
- "bytes": 211126,
110
- "sha256": "fcea9c493f3e895320eb15673580cdfebd6b8fd333cd90e2a11c0054fbe8317d"
109
+ "bytes": 217191,
110
+ "sha256": "1b02054111edf66b3a5766c7da74291fe597d2ed3ac7a89f0f282cfb99c415df"
111
111
  }
112
112
  ]
113
113
  }