fluncle 0.226.0 → 0.228.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.
Files changed (2) hide show
  1. package/bin/fluncle.mjs +63 -11
  2. package/package.json +1 -1
package/bin/fluncle.mjs CHANGED
@@ -612,7 +612,7 @@ function parseVersion(version) {
612
612
  var currentVersion;
613
613
  var init_version = __esm(() => {
614
614
  init_output();
615
- currentVersion = "0.226.0".trim() ? "0.226.0".trim() : "0.1.0";
615
+ currentVersion = "0.228.0".trim() ? "0.228.0".trim() : "0.1.0";
616
616
  });
617
617
 
618
618
  // src/update-notifier.ts
@@ -2454,7 +2454,7 @@ function parseVersion2(version) {
2454
2454
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2455
2455
  var init_version2 = __esm(() => {
2456
2456
  init_output();
2457
- currentVersion2 = "0.226.0".trim() ? "0.226.0".trim() : "0.1.0";
2457
+ currentVersion2 = "0.228.0".trim() ? "0.228.0".trim() : "0.1.0";
2458
2458
  });
2459
2459
 
2460
2460
  // ../../packages/registry/src/index.ts
@@ -2468,7 +2468,9 @@ var init_src = __esm(() => {
2468
2468
  MINUTE_MS = 60 * 1000;
2469
2469
  SURFACES = [
2470
2470
  {
2471
- exposedContent: ["the archive — every certified finding, newest first, cover-led"],
2471
+ exposedContent: [
2472
+ "the front door — search with real example queries, one edited lead finding, the newest findings, what just came out, and the four routes into the wider archive"
2473
+ ],
2472
2474
  kind: "web_route",
2473
2475
  name: "web.home",
2474
2476
  operatorNotes: "The Worker root. galaxy./radio. rewrite their root to /galaxy and /radio.",
@@ -2477,6 +2479,16 @@ var init_src = __esm(() => {
2477
2479
  url: `${SITE}/`,
2478
2480
  weights: { web: "primary" }
2479
2481
  },
2482
+ {
2483
+ exposedContent: ["the archive — every certified finding, newest first, cover-led"],
2484
+ kind: "web_route",
2485
+ name: "web.findings",
2486
+ operatorNotes: "The cover-led archive page the root used to be. Carries the Stories viewer (?story=<logId>, masked to /log/<id>); /?story= 301s to /log/<id>.",
2487
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2488
+ route: "/findings",
2489
+ url: `${SITE}/findings`,
2490
+ weights: { web: "primary" }
2491
+ },
2480
2492
  {
2481
2493
  exposedContent: [
2482
2494
  "the log index — every finding's coordinate page",
@@ -2657,11 +2669,26 @@ var init_src = __esm(() => {
2657
2669
  {
2658
2670
  discoveryUrl: `${SITE}/llms.txt`,
2659
2671
  exposedContent: [
2660
- "/tracks — the whole list: every track Fluncle holds (certified findings + the wider catalogue), newest release first, filterable by release year, tempo, key, label, and galaxy"
2672
+ "/search — the persistent, linkable search surface: one `?q=` carries a coordinate, an entity name, a natural-language filter, or a sonic reference, and the answer is server-rendered from the URL",
2673
+ "/search?q=<query> — a shareable, reload-safe result set over the whole archive (findings and the wider catalogue)"
2674
+ ],
2675
+ kind: "web_route",
2676
+ name: "web.search",
2677
+ operatorNotes: "The PERSISTENT half of search — the addressable counterpart to the ⌘K palette, which stays the accelerator and hands off here (components/search/search-command.tsx). It calls the SAME primitive, `searchArchive` (lib/server/search.ts), through a serverFn, so all four resolution tiers, the certified-first ranking, the catalogue rule, and the degradation contract are the ones docs/search.md already specifies; nothing is re-resolved. THE WHOLE QUERY STATE IS ONE PARAM, because the resolver takes one string — a coordinate, a name, a sentence, and a sonic reference all arrive as `q`. A coordinate/entity `redirect` is deliberately NOT followed here (the palette may; a persistent URL that bounced would be un-shareable and a back-button trap): the resolved row is rendered as the first result instead. The field commits on SUBMIT, never per keystroke, so history holds one entry per real query. It is a real `<form method=get action=/search>`, so search works with no JS. The BARE surface is indexable, in the sitemap, and carries the WebSite SearchAction; ANY `?q=` view is noindex, follow with its canonical collapsed onto the bare page (the /tracks filtered-view rule, and the standard posture for an internal results page). The INDEX is always-200, so it is HTTP-probeable.",
2678
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2679
+ route: "/search",
2680
+ url: `${SITE}/search`,
2681
+ weights: { web: "primary" }
2682
+ },
2683
+ {
2684
+ discoveryUrl: `${SITE}/llms.txt`,
2685
+ exposedContent: [
2686
+ "/tracks — the whole list: every track Fluncle holds (certified findings + the wider catalogue), newest release first, filterable by release year, tempo, key, label, and galaxy",
2687
+ "/track/:trackId — one archive recording: its artists, record, imprint, release date, tempo and key, a bounded preview, every service that carries it, and what sits close to it in sound"
2661
2688
  ],
2662
2689
  kind: "web_route",
2663
2690
  name: "web.tracks",
2664
- operatorNotes: "The top-level track index — the whole archive as one browse list, findings in full voice and the catalogue rows in the unlit register (DESIGN.md). Ordered by tracks.release_date (what came out), never findings.added_at (the Found Rule), numbered-paginated (?page=N) over the tracks_release_date_track_id_idx btree with a quiet YEAR fast lane, so a crawler with no JS walks the whole list (lib/server/tracks-hub.ts). The filter params MIRROR the search vocabulary verbatim (yearMin/yearMax, bpmMin/bpmMax, key, label; galaxy is the one extension). The bare HUB is always indexable + in the sitemap, each page self-canonical; ANY filter param present flips it to noindex, and paged bare URLs stay out of the sitemap. The INDEX is always-200, so it is HTTP-probeable.",
2691
+ operatorNotes: "The top-level track index — the whole archive as one browse list, findings in full voice and the catalogue rows in the unlit register (DESIGN.md). Ordered by tracks.release_date (what came out), never findings.added_at (the Found Rule), numbered-paginated (?page=N) over the tracks_release_date_track_id_idx btree with a quiet YEAR fast lane, so a crawler with no JS walks the whole list (lib/server/tracks-hub.ts). The filter params MIRROR the search vocabulary verbatim (yearMin/yearMax, bpmMin/bpmMax, key, label; galaxy is the one extension). The bare HUB is always indexable + in the sitemap, each page self-canonical; ANY filter param present flips it to noindex, and paged bare URLs stay out of the sitemap. The INDEX is always-200, so it is HTTP-probeable. The DETAIL page /track/:trackId is the archive recording's own destination, keyed on the row's permanent primary key; a CERTIFIED track 301s to /log/<coordinate> (that URL and its meaning are unchanged) and a stamped duplicate 301s to its principal. It carries its own EVIDENCE gate: one SQL expression (TRACK_PAGE_INDEXABLE_WHERE) drives both the page's robots directive and its sitemap membership, so a low-evidence page serves 200 + noindex,follow and stays out of the tracks sitemap child by construction. See docs/track-destination.md.",
2665
2692
  probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2666
2693
  route: "/tracks",
2667
2694
  url: `${SITE}/tracks`,
@@ -3978,6 +4005,23 @@ var init_src = __esm(() => {
3978
4005
  title: "Count check",
3979
4006
  weights: { status: "hidden" }
3980
4007
  },
4008
+ {
4009
+ command: "fluncle admin projections advance --target <public_aggregates|artist_qualification> --action repair --limit 500 --max-steps 4",
4010
+ exposedContent: [
4011
+ "keep the two public projection families converged after cutover with status-gated, bounded repair (--no-agent)"
4012
+ ],
4013
+ kind: "cron",
4014
+ name: "cron.projection-maintenance",
4015
+ operatorNotes: "every 5m with per-firing jitter, run by a rave-02 host systemd timer (docs/agents/hermes/projection-maintenance-timer/). Reads bounded projection status first. A dark shared cutover issues zero repair calls; an open idle cutover also writes nothing. Each affected public family gets at most four sequential repair pages of 500 subjects, with independent failure handling. Public aggregate repair also advances the bounded anchor cursor after marker debt drains. Rebuild, audit, track/crawl repair, and every cutover mutation remain operator-only. Zero LLM tokens; no new secret.",
4016
+ probeConfig: {
4017
+ cadenceMs: 5 * MINUTE_MS,
4018
+ cronName: "fluncle-projection-maintenance",
4019
+ kind: "cron"
4020
+ },
4021
+ statusDescription: "keeps the public catalogue projections caught up",
4022
+ title: "Projection upkeep",
4023
+ weights: { status: "hidden" }
4024
+ },
3981
4025
  {
3982
4026
  command: "fluncle admin reach collect",
3983
4027
  exposedContent: [
@@ -4223,7 +4267,7 @@ function checkBundleCompleteness(files) {
4223
4267
  uploadingFootage
4224
4268
  };
4225
4269
  }
4226
- async function trackVideoCommand(idOrLogId, files, onProgress, options = {}) {
4270
+ function validateBundleCompleteness(files, onProgress, options) {
4227
4271
  const completeness = checkBundleCompleteness(files);
4228
4272
  if (completeness.uploadingFootage && completeness.missingContract.length > 0 && !options.allowPartial) {
4229
4273
  throw new CliError2("bundle_incomplete", `Refusing to upload a PARTIAL bundle: footage is being uploaded but the re-render contract is missing ${completeness.missingContract.join(", ")}. ` + `A footage-only upload leaves composition.tsx/props.json/render.json stale on R2 and desyncs the render.json from the DB ledger. ` + `Ship the complete bundle (re-run \`ship\` and upload with --dir), or pass --allow-partial for a deliberate partial refresh (e.g. poster-only).`);
@@ -4239,6 +4283,9 @@ async function trackVideoCommand(idOrLogId, files, onProgress, options = {}) {
4239
4283
  for (const warning of completeness.plateWarnings) {
4240
4284
  onProgress?.(`warning: ${warning}`);
4241
4285
  }
4286
+ }
4287
+ async function trackVideoCommand(idOrLogId, files, onProgress, options = {}) {
4288
+ validateBundleCompleteness(files, onProgress, options);
4242
4289
  const present = VIDEO_FIELDS.map((spec) => ({
4243
4290
  field: spec.field,
4244
4291
  path: files[spec.option]
@@ -12493,6 +12540,7 @@ var init_admin_projections = __esm(() => {
12493
12540
  tags: ["Admin"]
12494
12541
  }).input(object({
12495
12542
  action: ProjectionStepActionSchema,
12543
+ includeStatus: boolean2().default(true),
12496
12544
  limit: number2().int().min(1).max(PROJECTION_STEP_LIMIT_MAX),
12497
12545
  target: ProjectionTargetSchema
12498
12546
  })).output(object({
@@ -12501,7 +12549,7 @@ var init_admin_projections = __esm(() => {
12501
12549
  ok: literal(true),
12502
12550
  processed: CountSchema,
12503
12551
  scheduled: CountSchema,
12504
- status: ProjectionStatusSchema,
12552
+ status: ProjectionStatusSchema.optional(),
12505
12553
  target: ProjectionTargetSchema
12506
12554
  }));
12507
12555
  setProjectionCutover = oc.route({
@@ -12781,7 +12829,9 @@ var init__shared = __esm(() => {
12781
12829
  logId: string2().optional(),
12782
12830
  releaseDate: string2().optional(),
12783
12831
  spotifyUrl: string2().optional(),
12784
- title: string2()
12832
+ title: string2(),
12833
+ trackId: string2(),
12834
+ url: string2().optional()
12785
12835
  }).meta({ id: "CatalogueTrackListItem" });
12786
12836
  FreshAlbumSchema = object({
12787
12837
  artists: array(string2()),
@@ -16578,17 +16628,19 @@ async function getProjectionStatusCommand() {
16578
16628
  }
16579
16629
  async function advanceProjectionCommand(input) {
16580
16630
  const { maxSteps = 1, target, ...body } = input;
16581
- let response = await adminApiPost(`/api/v1/admin/projections/${target}/advance`, body);
16631
+ const stepBody = maxSteps > 1 ? { ...body, includeStatus: false } : body;
16632
+ let response = await adminApiPost(`/api/v1/admin/projections/${target}/advance`, stepBody);
16582
16633
  let steps = 1;
16583
16634
  let processed = response.processed;
16584
16635
  let scheduled = response.scheduled;
16585
16636
  while (!response.complete && steps < maxSteps) {
16586
- response = await adminApiPost(`/api/v1/admin/projections/${target}/advance`, body);
16637
+ response = await adminApiPost(`/api/v1/admin/projections/${target}/advance`, stepBody);
16587
16638
  steps += 1;
16588
16639
  processed += response.processed;
16589
16640
  scheduled += response.scheduled;
16590
16641
  }
16591
- return { ...response, processed, scheduled, steps };
16642
+ const status = response.status ?? (await getProjectionStatusCommand()).status;
16643
+ return { ...response, processed, scheduled, status, steps };
16592
16644
  }
16593
16645
  async function setProjectionCutoverCommand(input) {
16594
16646
  const { enabled, target } = input;
package/package.json CHANGED
@@ -31,5 +31,5 @@
31
31
  "url": "git+https://github.com/mauricekleine/fluncle.git"
32
32
  },
33
33
  "type": "module",
34
- "version": "0.226.0"
34
+ "version": "0.228.0"
35
35
  }