今日の運動は腹筋ローラー,背筋,縄跳び(両足跳),散歩,ジョギング,ハンドグリップ,ダンベルをした。統計を 運動の記録 にまとめる。

変更登記の申請1

昨日 資本金の入金 を行ったので変更登記を申請する。法律では変更後2週間以内に申請しないといけない。9時過ぎに法務局へ行って申請した。オフィスから徒歩10分以内に法務局があるので気軽に行ける。

申請書を提出する手順は次になる。滅多に行かないからいつも忘れてしまっているが。

  1. 登録免許税の金額分の収入印紙を購入する
  2. 申請書の台紙に収入印紙を貼り付ける
  3. 法人登記専用受付へ行って申請書を提出する
  4. 受付スタッフが簡単に書類チェックして不備がなければ受付となる
    • 書類の内容は後日に別途審査される

4営業日以内に法務局から連絡がなければ変更登記は完了しているとみなせる。問題があれば提出して翌日か翌々日ぐらいには電話がかかってくる。

me-id 調査

選手からの me-id の調査の続き 。Microsoft Graph API の delta query という仕組みで entra id のユーザー/グループ情報に変更があったときにその差分を検出できる。ドキュメントはおそらく次の2つを参照すればよいはず。

もっとも API としての設計が開発者にとって直感的なものではないから、自分で動かしてデバッグしないとドキュメントだけで振る舞いを理解するのは至難だと思う。msgraph-sdk-go を使ったアプリケーションのデバッグをしていたときも実感したけど、azure のプラットフォームとしての品質は低い。ドキュメントがたまに間違っているし、仕様も振る舞いも論理的に説明がつかないこともある。開発者がちょっと触っただけで気付くというのは中身の品質はもっと低いと推測される。google や amazon の方が品質が高い。

実際に sdk を使って entra id のユーザーやグループの変更を行いながら、リクエストしてデバッグしてわかったことを書く。

  • nextLink と deltaLink は排他的な関係にある
    • nextLink が返される場合は、実際に差分データがなかったとしてもそのセッション内で次のデータがあることを意図している。nextLink が返され続けている限り、その情報を使ってリクエストし続ける必要がある。実際にはなにもデータが取得されないということもある。基本的には変更がなければ1回で終わるが、数十回ぐらいリクエストし続ける場合もある
    • deltaLink は rdbms でいうところの cursor のような概念になる。ldap プロトコルで言うところの cookie に相当する。その時点以降の差分のみを取得するためのトークンになる。deltaLink を使ったリクエストのときに新しい変更の差分情報を取得できる
    • 毎秒リクエストするような頻繁なリクエストだと、同じ変更差分を複数回取得することがある (at least once)
      • 30秒ごとに取得するようにしたら再現しなかったのでデータストアが eventual consistency な振る舞いをしている
  • 削除やメンバーの差分のような情報は sdk の api では additional data と呼ばれる領域に map で格納されている
    • ドキュメントではこれらをアノテーションと呼んでいた気がする
  • 削除は @removed というデータが含まれる
{
  "@removed": {"reason": "changed"}
}
  • メンバーの変更は members@delta というデータに含まれる
    • ユーザーとグループの関係を表すようなものは propertyName@delta というフィールドで additional data に含まれる
{
  "members@delta": [
    {
      "@odata.type": "#microsoft.graph.user",
      "id": "693acd06-2877-4339-8ade-b704261fe7a0"
    },
    {
      "@odata.type": "#microsoft.graph.user",
      "id": "49320844-be99-4164-8167-87ff5d047ace"
    }
  ]
}

たとえば、グループの変更とグループメンバーの変更を検出するには次のようなコードになる。これは振る舞いを検証するためのデバッグのために書いた検証コードなので実際のアプリケーションに使うようなものではない。

func groups() {
	defaultOptions := &azidentity.ClientSecretCredentialOptions{
		ClientOptions: azcore.ClientOptions{
			Retry: policy.RetryOptions{
				MaxRetries:    3,
				TryTimeout:    10 * time.Second,
				RetryDelay:    20 * time.Second,
				MaxRetryDelay: 80 * time.Second,
			},
		},
	}
	credential, err := azidentity.NewClientSecretCredential(
		tenantID, clientID, secret, defaultOptions,
	)
	if err != nil {
		log.Fatal(err)
	}
	client, err := msgraphsdk.NewGraphServiceClientWithCredentials(
		credential, defaultScopes,
	)
	if err != nil {
		log.Fatal(err)
	}

	ctx := context.Background()
	headers := abstractions.NewRequestHeaders()
	headers.Add("Prefer", "return=minimal")
	conf := &graphgroups.DeltaRequestBuilderGetRequestConfiguration{
		Headers: headers,
	}

	delta, err := client.Groups().Delta().GetAsDeltaGetResponse(ctx, conf)
	if err != nil {
		log.Fatal(err)
	}

	i := 0
	for {
		fmt.Println(i)

		fmt.Println("delta AdditionalData")
		ad := delta.GetAdditionalData()
		for k, v := range ad {
			if k == "@odata.context" {
				if s, ok := v.(*string); ok {
					fmt.Printf("%+v: %+v\n", k, *s)
				}
			} else {
				fmt.Printf("%+v: %+v\n", k, v)
			}
		}
		fmt.Println("------------------------")

		for _, d := range delta.GetValue() {
			fmt.Println("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^")
			id := d.GetId()
			if id != nil {
				fmt.Printf("id: %+v\n", *id)
			}

			fmt.Println("group AdditionalData")
			dad := d.GetAdditionalData()
			for k, v := range dad {
				if k == "@removed" {
					removed := v
					if removed != nil {
						for k, v := range removed.(map[string]any) {
							if reason, ok := v.(*string); ok {
								fmt.Printf("%+v: %+v\n", k, *reason)
							}
						}
						fmt.Println("------------------------")
					}
				} else if k == "members@delta" {
					fmt.Println("members@delta")
					ms := v.([]any)
					for _, mi := range ms {
						m := mi.(map[string]any)
						for k, v := range m {
							switch vv := v.(type) {
							case *string:
								fmt.Printf(" - %+v: %+v\n", k, *vv)
							case map[string]any:
								fmt.Printf(" - %+v: %+v\n", k, vv)
							}
						}
						fmt.Println("---------")
					}
				} else {
					fmt.Printf("%+v: %+v\n", k, v)
				}
			}
			dispName := d.GetDisplayName()
			if dispName != nil {
				fmt.Printf("dispName: %+v\n", *dispName)
			}
		}

		nextLink := delta.GetOdataNextLink()
		deltaLink := delta.GetOdataDeltaLink()
		if deltaLink != nil {
			fmt.Printf("hasNextLink: %t, use deltaLink to request\n", nextLink != nil)
			delta, err = client.Groups().Delta().WithUrl(*deltaLink).Get(ctx, conf)
			if err != nil {
				log.Fatal(err)
			}
			i++
			<-time.After(10 * time.Second)
			continue
		}

		delta, err = client.Groups().Delta().WithUrl(*nextLink).Get(ctx, conf)
		if err != nil {
			log.Fatal(err)
		}

		<-time.After(1 * time.Second)
		i++
	}
}

みなとのもりの運動

前回の所感 。いつも通りのメニューをこなす。19時頃に公園へ通うのも習慣になりつつある。今日もよい場所を陣取ったものの、後からフライングディスクのサークルの人たちが近くに来て練習を始めた。他が空いているのだからもう少し余裕をもって場所をとってくれたらよいのに、微妙に視界に入って気になる距離感のところで練習を始める。接触するわけではないから問題はないのだけど、フライングディスクというスポーツの特性上、ディスクが飛んできたら走ってくるから十分に離れていないと、たまたまディスクがそれたときに危なくないかが気になってしまう。だいたいストレッチと縄跳びとジョギングと筋トレとストレッチでだいたい1時間ぐらいかかる。だいぶ慣れてきたので、負荷を増やすためにもう少しメニューを追加してもよい時期かもしれない。