ampless 1.0.0-alpha.33 → 1.0.0-alpha.35

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/dist/index.js CHANGED
@@ -224,6 +224,14 @@ function definePlugin(p) {
224
224
  );
225
225
  }
226
226
  }
227
+ if (p.trust_level === "privileged") {
228
+ const hasHooks = p.hooks && Object.keys(p.hooks).length > 0 || (p.capabilities?.includes("eventHooks") ?? false);
229
+ if (hasHooks) {
230
+ console.warn(
231
+ `[ampless] Plugin "${p.name}" declares trust_level: 'privileged' with event hooks, but no privileged Lambda is provisioned yet. Hooks will not execute. Sync render surfaces (publicHead / metadata / publicBodyForPost / etc.) work normally regardless of trust_level. See docs/architecture/08-plugin-architecture.md#trust-levels for the future plan.`
232
+ );
233
+ }
234
+ }
227
235
  return p;
228
236
  }
229
237
 
@@ -279,11 +279,20 @@ runtime がチェックする内容:
279
279
 
280
280
  3 階層、選択基準は **イベントフック (hooks) が何を必要とするか** で決まります (sync サーフェスは IAM に触れない):
281
281
 
282
- | 階層 | IAM | 用途 |
283
- |---|---|---|
284
- | `untrusted` | なし (SQS consume のみ) | head/body descriptor、webhook 配送、コンテンツ変換 |
285
- | `trusted` | 投稿読み出し、`public/plugins/<instanceId ?? name>/...` への書き込み | RSS フィード、sitemap、計算済み JSON インデックス |
286
- | `privileged` | 予約 | 将来: SES、secret、private S3 |
282
+ | 階層 | IAM | 今日動くもの | 用途 |
283
+ |---|---|---|---|
284
+ | `untrusted` | なし (SQS consume のみ) | 同期サーフェス + イベントフック | head/body descriptor、webhook 配送、コンテンツ変換 |
285
+ | `trusted` | 投稿読み出し、`public/plugins/<instanceId ?? name>/...` への書き込み | 同期サーフェス + イベントフック | RSS フィード、sitemap、計算済み JSON インデックス |
286
+ | `privileged` | 予約 | 同期サーフェスのみ — イベントフックはサイレントフィルタ(警告ログあり) | 将来: SES、secret、private S3 |
287
+
288
+ > **`privileged` プラグイン作者へ:** `trust_level: 'privileged'` とイベント
289
+ > `hooks` を宣言した場合、**現時点でフックは実行されません**。両 processor が
290
+ > privileged プラグインをフィルタアウトし、一致する SQS イベントごとに
291
+ > `console.warn` を出力するため、サイレントドロップは可視化されます。
292
+ > 同期サーフェス(`publicHead`、`publicBodyEnd`、`metadata`、`publicBodyForPost`、
293
+ > `publicHtmlForPost`)は `trust_level` に関わらず正常に動き、警告も出ません。
294
+ > privileged Lambda が用意された際、`'privileged'` を宣言済みのプラグインは
295
+ > 自動的に新しい tier を使えるようになります — コードの変更は不要です。
287
296
 
288
297
  決め方の目安:
289
298
 
@@ -291,7 +300,7 @@ runtime がチェックする内容:
291
300
  - **hooks から投稿を読みたい (publish 時にフィードを再生成等)** → `trusted`
292
301
  - **`public/plugins/*` 以外への S3 PutObject や他の AWS API が必要** → 今はプラグインで ship せず、privileged 層を待つかプラグイン外で実装
293
302
 
294
- trust level がズレているプラグインは「権限不足で sliently fail」または「不要に強い権限を持つ」のどちらか。階層を切り替えて再デプロイすれば直ります。
303
+ trust level がズレているプラグインは「権限不足でサイレントに fail」または「不要に強い権限を持つ」のどちらか。階層を切り替えて再デプロイすれば直ります。
295
304
 
296
305
  ---
297
306
 
@@ -563,6 +572,50 @@ React 19 はさらに、クライアントコンポーネントのレンダー
563
572
 
564
573
  投稿単位の見える出力には `publicHtmlForPost` を使ってください(Phase 6d — 上の例と §6 の `PublicPostHtmlDescriptor` 参照)。runtime が post 本文の周囲の固定スロットにサーバーサイド HTML を出すので、hydration と競合しません。
565
574
 
575
+ ---
576
+
577
+ ## 6a. 予約投稿とコンテンツイベント
578
+
579
+ ampless は**予約投稿**をサポートしています。`status: 'published'` かつ `publishedAt` が未来の投稿は、その時刻まで公開読み出しから隠されます。`publishedAt` を過ぎると、サイトの自然なキャッシュ有効期限(デフォルト ≤ ~5 分)の範囲内で公開されます — 秒単位の正確なトリガーはありません。
580
+
581
+ ### イベントは保存時に発火する(`publishedAt` のタイミングではない)
582
+
583
+ `content.published`(および `content.updated`)は DynamoDB Streams 経由で**投稿が保存されたとき**に emit されます。`publishedAt` が到来したときではありません。つまり、`content.published` に反応するプラグインは、投稿が未来日時の場合、**その投稿が公開される前**に実行されます。
584
+
585
+ 現在の投稿一覧から公開アセットを再構築する trusted プラグイン(RSS、sitemap、JSON インデックスなど)にとってはこれは問題ありません — `listPublishedPosts()` が未来日時の投稿をすでに除外しているため、再生成されたアセットはその投稿が公開されるまでリストに含まれません。
586
+
587
+ 一方、**外部通知プラグイン**(webhook、プッシュ通知、SNS 投稿など)には影響があります。投稿の URL が 404 を返しているまたはホームページにリダイレクトされている間に通知が配信されてしまいます。
588
+
589
+ ### 推奨パターン: `publishedAt` でゲートする
590
+
591
+ ディスパッチの前に `event.payload.publishedAt` を確認します。投稿が未来日時の場合はスキップまたは保留します:
592
+
593
+ ```ts
594
+ hooks: {
595
+ async 'content.published'(event, ctx) {
596
+ const { publishedAt } = event.payload
597
+
598
+ // 予約投稿には通知を送らない。イベントは保存時に発火するが、
599
+ // 投稿が公開されるのは publishedAt になってから。
600
+ if (publishedAt && new Date(publishedAt) > new Date()) {
601
+ return
602
+ }
603
+
604
+ // 投稿は今すぐ公開状態 — 通知しても安全。
605
+ await sendWebhook(event.payload, ctx)
606
+ },
607
+ }
608
+ ```
609
+
610
+ イベントペイロード内の `publishedAt` は UTC ISO 8601 文字列(`...Z`)です。`Date.now()` と比較する前に `new Date()` またはお好みの日付ライブラリでパースしてください。
611
+
612
+ ### 今後の対応
613
+
614
+ `content.published` の発火を保存時ではなく `publishedAt` のタイミングに合わせる機能 — EventBridge Scheduler または DynamoDB TTL トリガー Lambda によるスケジューラーコンポーネントが必要 — は計画中の機能強化です。現リリースのスコープには含まれていません。それまでの間は上記のパターンが通知プラグインにおける推奨ガードです。
615
+
616
+ オペレーター視点からの `publishedAt` セマンティクスの全体像は [`docs/scheduled-publishing.md`](https://github.com/heavymoons/ampless/blob/main/docs/scheduled-publishing.ja.md) を参照してください。
617
+
618
+ ---
566
619
 
567
620
  ### CSP nonce(Phase 1 予約)
568
621
 
@@ -341,11 +341,22 @@ Three tiers, picked by what the plugin needs to do **inside
341
341
  event hooks** (the sync surfaces — metadata, head, body — don't
342
342
  touch IAM):
343
343
 
344
- | Tier | IAM | Used by |
345
- |---|---|---|
346
- | `untrusted` | none (SQS consume only) | head/body descriptors, webhook delivery, content transforms |
347
- | `trusted` | read posts, write `public/plugins/<instanceId ?? name>/...` | RSS feed, sitemap, computed JSON indexes |
348
- | `privileged` | reserved | future: SES, secrets, private S3 |
344
+ | Tier | IAM | What runs today | Used by |
345
+ |---|---|---|---|
346
+ | `untrusted` | none (SQS consume only) | sync surfaces + event hooks | head/body descriptors, webhook delivery, content transforms |
347
+ | `trusted` | read posts, write `public/plugins/<instanceId ?? name>/...` | sync surfaces + event hooks | RSS feed, sitemap, computed JSON indexes |
348
+ | `privileged` | reserved | sync surfaces only — event hooks are silently filtered out (warning logged) | future: SES, secrets, private S3 |
349
+
350
+ > **Warning for `privileged` plugin authors:** If you declare
351
+ > `trust_level: 'privileged'` with event `hooks` today, **your hooks
352
+ > WILL NOT EXECUTE**. Both event processors filter out privileged
353
+ > plugins and emit a `console.warn` on every matching SQS event so
354
+ > the drop is visible. Sync render surfaces (`publicHead`,
355
+ > `publicBodyEnd`, `metadata`, `publicBodyForPost`, `publicHtmlForPost`)
356
+ > work normally regardless of `trust_level` and emit no warning.
357
+ > When the privileged Lambda provisioning PR lands, plugins that
358
+ > already declared `'privileged'` will automatically pick up the new
359
+ > tier — nothing needs to change in your code.
349
360
 
350
361
  Rule of thumb:
351
362
 
@@ -719,6 +730,72 @@ the example above and §6's `PublicPostHtmlDescriptor`). The runtime
719
730
  emits server-side HTML at fixed slots around the post body, so
720
731
  nothing races against hydration.
721
732
 
733
+ ---
734
+
735
+ ## 6a. Scheduled posts and content events
736
+
737
+ ampless supports **scheduled publishing**: a post with `status:
738
+ 'published'` and a future `publishedAt` is hidden from all public
739
+ reads until that time. Once `publishedAt` arrives, the post becomes
740
+ visible within the site's natural cache window (≤ ~5 minutes by
741
+ default) — there is no exact-time trigger.
742
+
743
+ ### Events fire at save time, not at `publishedAt`
744
+
745
+ `content.published` (and `content.updated`) are emitted via DynamoDB
746
+ Streams **when the post is saved**, not when `publishedAt` arrives.
747
+ This means a plugin that reacts to `content.published` will run
748
+ **before the post is publicly visible** when the post is future-dated.
749
+
750
+ For trusted plugins that rebuild public assets from the current post
751
+ list (RSS, sitemap, JSON indexes), this is harmless — `listPublishedPosts()`
752
+ already filters out future-dated posts, so the regenerated asset
753
+ simply omits the scheduled post until it goes live.
754
+
755
+ For **outbound-notification plugins** (webhook, push notification,
756
+ social post) the early fire matters: the notification will be
757
+ delivered to subscribers while the post URL still returns 404 or
758
+ redirects to the home page.
759
+
760
+ ### Recommended pattern: gate on `publishedAt`
761
+
762
+ Check `event.payload.publishedAt` before dispatching. Skip or defer
763
+ when the post is future-dated:
764
+
765
+ ```ts
766
+ hooks: {
767
+ async 'content.published'(event, ctx) {
768
+ const { publishedAt } = event.payload
769
+
770
+ // Skip notification for future-scheduled posts. The event fires
771
+ // at save time, but the post won't be public until publishedAt.
772
+ if (publishedAt && new Date(publishedAt) > new Date()) {
773
+ return
774
+ }
775
+
776
+ // Post is live now — safe to notify.
777
+ await sendWebhook(event.payload, ctx)
778
+ },
779
+ }
780
+ ```
781
+
782
+ The `publishedAt` value in the event payload is a UTC ISO 8601 string
783
+ (`...Z`). Parse it with `new Date()` or your preferred date library
784
+ before comparing against `Date.now()`.
785
+
786
+ ### Future work
787
+
788
+ Aligning event emission with the scheduled time — so `content.published`
789
+ fires at `publishedAt` rather than at save time — is a planned
790
+ enhancement. It requires a scheduler component (EventBridge Scheduler
791
+ or a DynamoDB TTL-triggered Lambda) and is not yet in scope for the
792
+ current release. Until then, the pattern above is the recommended
793
+ guard for notification plugins.
794
+
795
+ For a full description of `publishedAt` semantics from the operator's
796
+ perspective, see [`docs/scheduled-publishing.md`](https://github.com/heavymoons/ampless/blob/main/docs/scheduled-publishing.md).
797
+
798
+ ---
722
799
 
723
800
  ### CSP nonce (Phase 1 reservation)
724
801
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ampless",
3
- "version": "1.0.0-alpha.33",
3
+ "version": "1.0.0-alpha.35",
4
4
  "description": "Serverless CMS for AWS Amplify",
5
5
  "license": "MIT",
6
6
  "type": "module",